有一天,我在百忙之中抽空去StackOverflow上刷声望。因此,我看到了以下问题:如何将字节数输出为人类可读的格式?也就是说,如何将123,456,789字节输出成123.5MB?隐含条件是结果字符串应在1到999.9范围内,后跟适当的单位后缀。这个问题已经有了答案,代码是用循环写的。基本思路很简单:尝试所有尺度,从最大的EB(10^18字节)开始向下到最小的B(1字节),选择第一个小于字节数的尺度。用伪代码表示,大致如下:suffixes=["EB","PB","TB","GB","MB","kB","B"]magnitudes=[1018,1015,1012,109,106,103,100]i=0while(ibyteCount)i++printf("%.1f%s",byteCount/magnitunes[i],后缀[i])通常,如果一个问题已经有了正确答案并且有人喜欢它,其他答案将很难赶上。虽然这个答案有一些问题,所以我仍然有机会超越它。至少,循环有足够的空间来清理。1.这只是一道代数题!然后想到kB、MB、GB等后缀只是1000的幂(或者IEC标准下的1024),也就是说不用循环,可以用对数来计算正确的后缀。基于这个想法,我写了下面的答案:publicstaticStringhumanReadableByteCount(longbytes,booleansi){intunit=si?1000:1024;if(bytes=Math.ceil(Math.pow(unit,exp)*(unit-0.05)))exp++;5、多9但是,当输入为999,949,999,999,999,999时,结果为1000.0PB,而正确结果为999.9PB。这段代码在数学上是正确的,那么问题是什么?至此我们已经达到了double类型精度的上限。关于浮点运算根据IEEE754的浮点数表示方法,接近0的数非常密集,而非常大的数非常稀疏。事实上,超过一半的值位于-1和1之间,像Long.MAX_VALUE这样大的数字对双精度没有任何意义。用代码表示就是doublea=Double.MAX_VALUE;双b=a-Long.MAX_VALUE;System.err.println(a==b);//printstrue有两个计算有问题:String.formatparameterstrigger中的trigger给exp的结果加了一个临时阈值。当然可以改成BigDecimal,但是有什么意义呢?而改成BigDecimal的代码会变得更乱,因为标准的API没有BigDecimal的对数功能。Shrinkingintermediatevalues对于第一个问题,我们可以将bytes值收缩到一个精度更好的范围内,并相应地调整exp。由于最终结果总是四舍五入,因此丢弃最低有效位并不重要。if(exp>4){bytes/=unit;exp--;}adjusttheleastsignificantbits对于第二个问题,我们需要关心最低有效位(999,949,99...9和999,950,00...0等不同的幂值),因此需要不同的方法用来解决它。首先请注意,有12种不同的阈值情况(每种模式有6种),其中只有一种有问题。有问题的结果的十六进制表示以D00结尾。如果发生这种情况,只需调整到正确的值即可。longth=(long)Math.ceil(Math.pow(unit,exp)*(unit-0.05));if(exp<6&&bytes>=th-((th&0xFFF)==0xD00?51:0))表达式++;由于它依赖于浮点结果中的特定位模式,因此需要strictfp以确保它在任何硬件上都能正常工作。6.负数输入虽然不清楚什么情况下会使用负数字节,但由于Java没有unsignedlong,所以最好处理复数。现在,-10,000产生-10000B。引入absBytes变量:longabsBytes=bytes==Long.MIN_VALUE?Long.MAX_VALUE:Math.abs(字节);表达式非常复杂,因为-Long.MIN_VLAUE==LONG.MIN_VALUE。以后你会用absBytes而不是bytes来计算exp。7.最终版本这是最终版本的代码://来自:https://programming.guide/worlds-most-copied-so-snippet.htmlpublicstaticstrictfpStringhumanReadableByteCount(longbytes,booleansi){int单位=si?1000:1024;长absBytes=字节==Long.MIN_VALUE?Long.MAX_VALUE:Math.abs(字节);if(absBytes=th-((th&0xFFF)==0xD00?51:0))exp++;字符串pre=(si?"kMGTPE":"KMGTPE").charAt(exp-1)+(si?"":"i");if(exp>4){bytes/=unit;exp-=1;}returnString.format("%.1f%sB",bytes/Math.pow(unit,exp),pre);}这个答案原本只是为了避免循环和过多的分支。具有讽刺意味的是,在考虑了各种边缘情况后,这段代码比原来的答案更难理解。我绝对不会在生产中使用此代码。总之,即使有几千个赞,StackOverflow上的代码也会有问题。测试所有边缘情况,尤其是从StackOverflow复制和粘贴的代码。浮点运算很难。复制代码时一定要注明出处。其他人可以用它来提醒您重要的事情。