一时复制粘贴,bug火葬场频发。对于开发者来说,StackOverflow和GitHub是最熟悉的两个平台。这些平台上充斥着大量的开源项目信息和代码片段,用来解决各种问题。最近,一位名叫Aioobe的开发者在一次调查中发现了一段他十年前写的代码。这段代码已经成为StackOverflow上被复制最多、传播最广的答案。此代码存在。但是,开发人员说这段代码实际上是有问题的,并且最近更新了带有解释的答案。这段代码是做什么用的?早在2010年,我就整天在StackOverflow上回答问题,希望提升自己的知名度。当时,一个问题引起了我的注意:如何以人类可读的格式输出字节数?例如,将“123456789字节”转换为“123.5MB”格式输出。这是现在的屏幕截图,但问题实际上是这里的隐式范例是结果字符串值应介于1和999.9之间,后跟适当大小的单位。已经有人回应了。答案中的代码是基于循环的,基本思路很简单:尝试所有的单位,从最大的(EB,也就是1018字节)到最小的(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/magnitudes[i],suffixes[i])一般来说,如果你发布正确答案已经取得正分,后来者难追。在StackOverflow上,这被称为“最快拔枪获胜”。但是,我认为这个答案是有缺陷的,所以我要重做。我意识到所有单位的性质,无论是KB、MB还是GB,实际上都是1000的幂(当然,IEC标准是1024),这意味着应该可以使用对数而不是周期单位来计算正确的量级.基于以上思路,我发表了以下内容:数学.log(unit));Stringpre=(si?"kMGTPE":"KMGTPE").charAt(exp-1)+(si?"":"i");returnString.format("%.1f%sB)",bytes/Math.pow(unit,exp),pre);}当然这段代码可读性不是很好,log/pow也可能一定程度上影响执行效率,但至少这里没有循环,而且几乎没有Branch,我觉得还是比较整洁的。这里使用的数学非常简单。字节数表示为byeCount=1000s,其中s表示小数点后的位数(二进制表示,以1024为底),求解s,可以得到s=log1000(byteCount)。API中没有现成的log1000可以直接使用,我们不妨用自然对数表示,即s=log(byteCount)/log(1000)。接下来,我们将s作为基数(即舍入为整数),因为如果我们得到的结果大于1MB(但小于1GB),我们希望继续使用MB作为表示单位。此时,若s=1,单位为KB;如果s=2,单位是MB;以此类推,我们将byteCount值除以1000s,然后取对应的单位。接下来,我所能做的就是等待,看看社区是否喜欢这个答案。当时,我绝不会想到它会成为StackOverflow上被复制最多的代码片段。错误在哪里?估计很多人看到这里一定在想,这段代码到底是什么bug?再看代码:publicstaticStringhumanReadableByteCount(longbytes,booleansi){intunit=si?1000:1024;if(bytes=Math.pow(unit,exp)*(unit-0.05))exp++;调整后,代码可以正常工作,直到字节数接近1EB。以输入999,949,999,999,999,999为例,当前结果为1000.0PB,但正确结果应该是999.9PB。但是从数学上讲,代码的结果是准确的,那又是怎么回事呢?在这里,我们遇到了双精度(double)精度机制的局限性。浮点运算基础由于IEEE754表示,接近零的浮点值可能非常密集,但大值可能非常稀疏。事实上,所有浮点值的一半都在-1和1之间;当涉及到大的双精度浮点数时,像Long.MAX_VALUE这样大的值就没有任何意义了。double1=Double.MAX_VALUE;doubl2=l1-Long.MAX_VALUE;System.err.println(l1==l2);//printstrue我们来看两个有问题的计算:String.format参数中的除法;expcarrythreshold当然我们可以切换到BigDecimal,但那会很无聊。另外,由于标准API中没有BigDecimallog函数,所以问题实际上仍然存在。Shrinkingintermediatevalues对于第一个问题,我们可以将byte值收缩到更合理的精度范围内,同时相应地调整exp。无论如何,最终结果都是四舍五入的,所以我们所要做的就是不四舍五入最低有效数字。if(exp>4){bytes/=unit;exp--;}调整单元末尾的最低有效位),因此必须考虑不同的解决方案。首先,我们注意到阈值有12个不同的可能值(每个模式6个),并且只有其中一个最终会失败。错误的结果可以通过以D0016结尾的符号准确识别。一旦发生这种情况,我们只需将其调整为正确的值即可。length=(long)(Math.pow(unit,exp)*(unit-0.05));if(exp<6&&bytes>=th-((th&0xFFF)==0xD00?52:0))exp++;由于我们在浮点结果中需要使用特定的数字模式,所以入手的对象自然是strictfp,旨在保证不受硬件运行代码的影响。负输入我没有想到可能需要使用负字节大小的情况,但是由于Java不支持无符号长整型,所以最好考虑到这一点。现在,如果输入是-10000,结果是-10000B。这里我们引入absBytes:longabsBytes=bytes==Long.MIN_VALUE?Long.MAX_VALUE:Math.abs(bytes);这里的表达式之所以这么复杂,是因为-Long.MIN_VALUE==Long.MIN_VALUE。现在,我们使用absBytes而不是bytes来执行所有与exp相关的计算。最终版本这里是代码片段的最终版本,它在原始版本的基础上进行了仔细的调整和改进://From:https://programming.guide/the-worlds-most-copied-so-snippet.htmlpublicstaticstrictfpStringhumanReadableByteCount(longbytes,booleansi){intunit=si?1000:1024;longabsBytes=bytes==Long.MIN_VALUE?Long.MAX_VALUE:Math.abs(bytes);if(absBytes=th-((th&0xfff)==0xd00?52:0))exp++;Stringpre=(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);}请注意,这段代码的最初目标是避免循环以及大量分支带来的复杂性。在修复所有极端情况后,新代码的可读性不如原来的代码。就个人而言,我绝对不会将此代码复制到生产代码中。这段代码复制到哪里?此前,一位名叫SebastianBaltes的博士生在《Empirical Software Engineering》上发表了一篇题为《GitHub 项目中 Stack Overflow 代码片段的用法与归因》的论文。文章的核心话题只有一个:用户对代码片段的引用是否遵循StackOverflow的CCBY-SA3.0许可,即用户从StackOverflow复制代码时应保证何种级别的归属?在他们的分析中,作者从StackOverflow数据转储中提取代码片段,并将它们与公共GitHub存储库中的代码进行匹配。以下是该论文的基本发现:我们进行了一项大规模的实证研究,分析了来自各种公共GitHub项目的非常规Java代码片段,使用并归因于实际源自StackOverflow调查的代码片段。这篇文章给出了一个表格,ID为3758880的答案是我几年前发的那个。到目前为止,这个回答已经获得了数十万的浏览量和一千多条赞。只需在GitHub上随机搜索,您就会发现数以千计对humanReadableByteCount的引用。这也意味着这段有问题的代码被无数项目和开发者引用。要验证此代码是否也在您的本地存储库中,请执行以下操作:$gitgrephumanReadableByteCount大多数开发人员在StackOverflow上的代码片段可能存在错误,即使获得无数赞誉也无法改变这一事实;确保测试所有极端情况,尤其是那些从StackOverflow复制的代码;浮点运算比较复杂,难度也很大,复制代码时请务必理解代码背后的逻辑和使用规范。