原文:https://programming.guide/worlds-most-copied-so-snippet.html

在最近一项名为“Stack Overflow代码片段在GitHub项目中的使用和归属情况”的研究里,我于十年前写下的代码竟是StackOverflow上被复制最多的的回答。讽刺的是,这份代码是有bug的。

很久很久以前…

2010年,我坐在自己的办公室里,做着我本不应该做的事情:摸鱼以及在Stack Overflow上刷声望。

结果下面这个问题吸引住了我的眼球:如何以人类可读的格式输出字节数?也就是把123,456,789个字节转换成“123.5MB”的格式。

这一问题隐含的条件是,返回的字符串的字面值应该在1到999.9之间,附加一个合适的单位作为后缀。

当时已经有人做出了回答。这个答案基于一个循环,原理很简单:从最大的量级(EB=1018字节)到最小的量级(B=1字节)逐一尝试一遍,然后选择第一个比字节数小的单位。伪代码差不多如下所示:

  1. suffixes = [ "EB", "PB", "TB", "GB", "MB", "kB", "B" ]
  2. magnitudes = [ 1018, 1015, 1012, 109, 106, 103, 100 ]
  3. i = 0
  4. while (i < magnitudes.length && magnitudes[i] > byteCount)
  5. i++
  6. printf("%.1f %s", byteCount / magnitudes[i], suffixes[i])

通常来说,正确的答案已经po出来了,而且回答的得分也是正数,就很难再去填补什么了。用Stack Overflow的黑话来说就是“西部问题的第一快枪手”。就本题而言,这个回答还是有几处问题的,所以我觉得有机会去击败它。至少这个基于循环的代码可以被整理一番。

算数么,我会这个!

然后我就意识到,kB,MB,GB,…这些后缀不就是1000的次方么(用IEC标准就是1024),也就是说可以用算法来得到正确的后缀,而不是用循环去试出来。

靠着这个想法,我po出来了如下代码:

  1. public static String humanReadableByteCount(long byte, boolean si){
  2. int unit = si ? 1000 : 1024;
  3. if (bytes < unit) return bytes + "B";
  4. int exp = (int) (Math.log(bytes) / Math.log(unit));
  5. String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
  6. return String.format(".1f %sB", bytes / Math.pow(unit, exp), pre);
  7. }

诚然这份代码并不适合阅读,而且log / pow可能使其效率低于其他解答。但是这里几乎没有分支也没有循环,我觉得还算挺不错的。

这份代码背后的数学原理挺直白的。字节数可以表示成byeCount = 1000s的形式,其中s代表scale量级。(要是用二进制字节计数,基数使用的就会是1024。)然后就是,给定s=log1000(byteCount), 解 s

API里并没有log1000()这样的函数,但是我们可以用自然对数的形式表达:s=log(byteCount) / log(1000)。然后进行地板除(强转int),因为如果是超过1MB但是小于1GB使用的还是MB量级。

这个时候,s=1就是kb,s=2就是MB,如此类推。最后把原字节数除以1000s,加上对应的后缀即可。

接下来我能做的就是一个字:等,等到社区赞赏这份答案。而我没有意识到的是,这份答案竟会成为Stack Overflow上被复制最多的答案。

归属研究

出Bug了

9999999999…

99999999999999999999999999….

负数输入

最终版

关键点总结

  • Stack Overflow上的回答就算赞数多也可能有bug。
  • 测试所有边缘情况,尤其是复制粘贴Stack Overflow上的代码时。
  • 浮点数运算没那么简单。
  • 复制代码时把代码归属人也加上。不然小心自己背锅。(译者 但你复制bug不检查的责任还是少不了的啊)