在最近的一项研究中标题的使用和归属堆栈溢出代码片段在GitHub项目中,我几乎写了几十年前的答案是堆栈溢出上最多复制的片段。具有讽刺意味的是,它恰好是越野车。
回到2010年我坐在办公室里,做我不应该做的事情:代码打高尔夫球和追逐堆栈溢出。
以下问题引起了我的注意:如何以人类可读格式打印字节数?也就是说,您如何格式化123,456,789字节,如“123.5 MB”。
这里隐式规范在于,生成的字符串应在1到999.9之间具有值,然后是具有适当幅度的后缀。
一个答案已经发布。该答案中的代码基于循环。这个想法很简单:尝试所有大小,从最大(eb = 10 18字节)到最小(b = 1字节),并使用小于字节数的第一个。在伪代码中,它看起来像这样:
后缀= [" eb&#34 ;,#34; pb&#34 ;," tb&#34 ;," gb&#34 ;," mb&#34 ;,&# 34; KB&#34 ;," B"估计= [1018,1115,1012,109,106,10 3,10 0] i = 0,而(i< rt; leng&&& bytecount)i + + printf("%。1f%s",bytecount / mangitudes [i],后缀[i])
通常在已经有一个正确的答案时已经有了正面分数,很难赶上它。在堆栈溢出Lingo,它被称为西部问题最快的枪。在这种情况下,现有答案有一些缺陷,所以我仍然看到了一个机会来实现它。至少,基于循环的代码可以显着清理。
然后它打了我。 KB,MB,GB,...后缀只不过是1000(或IEC标准中的1024)的权力,这意味着应该使用Logarithms而不是循环计算右后缀。
public static字符串HumanReadableByTecount(长字节,布尔SI){int单位= si? 1000:1024; if(字节<单位)返回字节+" B&#34 ;; int exp =(int)(数学。日志(bytes)/ math。日志(单位)); String pre =(si?" kmgtpe":" kmgtpe")。特征(exp-1)+(Si?"":" I");返回字符串。格式("%。1f%sb",字节/数学。POW(单位,exp),pre);}
授予它不是很可读,日志/战斗可能比其他解决方案效率较低。但是没有循环,几乎没有我认为的分支是非常整洁的。
背后的数学是直截了当的。字节计数表示为ByTecount = 1000 s,其中s表示比例。 (对于二进制表示法,使用基地1024。)解决S = Log 1000(ByTecount)。
API中没有可用日志1000,但我们可以以自然对数表示如下S = log(Bytecount)/ log(1000)。然后,我们的地板s(转换为int),如果我们例如拥有多个兆字节(但不是完整的千兆字节),我们希望使用MB的幅度。
此时如果s = 1刻度是千字节,如果s = 2刻度为兆字节,等等。我们将ByTecount分为1000 s并在相应的字母前缀上拍摄。
我现在所能做的就是等待,看看社区是否会欣赏答案。我知道这将成为堆栈溢出的最多复制的片段。
快进至2018年。由Sebastian Baltes的博士学位学生在经验软件工程学杂志上发表一篇论文。标题是GitHub项目中堆栈溢出代码片段的使用和归因,它基本上尝试回答一个问题:是堆栈溢出的CC By-SA 3.0许可证尊重吗? IE。在从堆栈溢出中复制代码时,在多大程度上给出了适当的归因。
作为分析的一部分,它们从堆栈溢出数据转储中提取了代码片段,并将其与公共Github Repos的代码匹配。引用摘要:
我们提出了大规模的仿真静态的结果分析了非琐碎的Java代码片段的使用和归属,从而在公共Github(GH)项目中的答案。
在上面的答案,ID 3758880的答案恰好是我八年前发布的答案。此时,答案超过了数十万个观点和超过一千个起伏。
要检查您是否碰巧在本地检查的repo中有代码:
塞巴斯蒂安在OpenJDK存储库中找到了一场比赛。没有属性,并且OpenJDK许可证与CC By-SA 3.0不兼容。他到达DEV邮件列表询问堆栈溢出上的代码是否已从OpenJDK复制,或者如果它是另一种方式。
这里有趣的部分是我曾经在Oracle上工作,在Openjdk项目上,所以我的前同事和我的朋友回答说:
为什么不要求这么帖子(Aioobe)直接询问这件帖子?他是一个OpenJDK贡献者,在OperJDK源代码中出现此代码时,Oracle就业。
甲骨文不轻易采取这些东西。我碰巧知道甲骨文中的一些人在阅读这个回复时叹了口气,并在“指控”之后看到它是一点胜利。
塞巴斯蒂安然后伸出我伸直,我所做的是:当该提交合并时,我尚未开始Oracle,我没有贡献那个补丁。在Oracle上笑话。不久之后,提交了一个问题并删除了代码。
我打赌你已经给了它一些想法。代码片段中的那个错误是什么?
public static字符串HumanReadableByTecount(长字节,布尔SI){int单位= si? 1000:1024; if(字节<单位)返回字节+" B&#34 ;; int exp =(int)(数学。日志(bytes)/ math。日志(单位)); String pre =(si?" kmgtpe":" kmgtpe")。特征(exp-1)+(Si?"":" I");返回字符串。格式("%。1f%sb",字节/数学。POW(单位,exp),pre);}
在exabytes后,10 18,Zettabytes,10 21.这可能是一个非常大的输入导致" kmgtpe&#34的界限索引;细绳?不。最大长值为2 63 - 1≈9.2×10 18,因此没有长的价值将超越EB。
它可以是Si和二进制之间的混合吗?不。在答案的早期版本中有一个混合,但这是很快的修复。
exp最终是0,导致charat(exp-1)失败吗?不。第一个IF-陈述涵盖了这种情况。 EXP值总是至少为1。
输出中可能会有一些奇怪的圆形错误吗?现在我们到了那里......
解决方案在接近1 MB之前一直工作。当给出999,999个字节作为输入时,结果(在SI模式下)是" 1000.0 KB"虽然999,999更接近1,000×1000 1,而不是999.9×1000 1,但根据规格,1,000“有效”超出范围。正确的结果是" 1.0 MB"
FWIW,所有22个答案都发布,包括使用Apache Commons和Android库的答案,在撰写本文时具有此错误(或IT的变体)。
那么我们如何解决这个问题?首先,我们注意到,一旦字节数更接近1×1,000 2(1 MB),指数(EXP)应该从'k'变为'm',而不是999.9×1000 1(999.9 k)。这发生在999,950。同样,当我们通过999,950,000岁时,我们应该从'm'切换到'g'。
为实现此目的,如果字节更大,则计算此阈值和Bump exp。 (对于二进制案例,此阈值不是一个整数,我们需要CEIL结果。)
通过此更改,代码一直运行,直到字节计数接近1 eb。
鉴于输入999,949,999,999,999,999,结果现在是1000.0pb,而正确的结果是999.9 pb。数学上的代码是准确的,所以这里发生了什么?
由于IEEE 754表示浮点值接近零是非常密集的,并且大的值非常稀疏。事实上,所有值的一半都在-1到1之间找到,并且在谈论大的双打时,一个像long.max_value一样大的价值意味着什么。字面上地。
我们可以切换到BigDecimal,但在那里的乐趣在哪里?!此外,无论如何,它都会变得凌乱,因为标准API中没有大型日志功能。
对于第一个问题,我们可以将字节值缩小到精度更好的范围,并相应地调整exp。最终结果无论如何都是舍进的,因此我们并不重要,我们抛出最低有效数字。
对于第二个问题,我们关心最低有效位(999,949,99 ... 9和999,950,00 ... 0应该最终以不同的指数结束)所以这个问题要求不同的解决方案。
首先,我们注意到,对于阈值有12个不同的可能值(每种模式6),并且只有其中一个最终有故障。错误的结果可以唯一地确定它以D00 16结束。如果是这种情况,我们只需将其调整到正确的值。
长期=(长)数学。 CEIL(数学。战俘(单位,exp)*(单位 - 0.05)); if(exp< 6& bytes> = th - ((th& 0xfff)== 0xd00?51:0))exp ++;
由于我们依赖于浮点结果中的特定位模式,因此我们在strictfp上打击以确保它不论运行代码的硬件如何运行。
在否定的情况下,尚不清楚的是,消极的字节数可能是相关的,但由于Java没有毫无符号,我们更好地处理它。现在是-10000 b中的输入如-10,000个结果。
复杂的表达是因为 - 延伸了--long.min_value == long.min_value。现在我们使用Absbytes而不是字节执行与exp相关的所有计算。
这是代码的最终版本,以原始版本的精神打高尔米德和压缩:
//来自:https://programming.guide/worlds-most-copied-.so-snippet.html公共静态strictfp字符串HumanReadableByTecount(Long字节,Boolean Si){int单位= si? 1000:1024; long absbytes = bytes == long。 min_value?长。 max_value:数学。 ABS(字节); if(absbytes<单位)返回字节+" B&#34 ;; int exp =(int)(数学。日志(absbytes)/ math。日志(单位));长期=(长)数学。 CEIL(数学。战俘(单位,exp)*(单位 - 0.05)); if(exp< 6& absbytes> = th - ((th& 0xfff)== 0xd00?51:0))Exp ++; String pre =(si?" kmgtpe":" kmgtpe")。 Charat(exp - 1)+(SI?"":" I"); if(exp> 4){bytes / =单位; Exp - = 1; }返回字符串。格式("%。1f%sb",字节/数学。POW(单位,exp),pre);}
请注意,这开始避免环路和过度分支的挑战。在熨烫后,所有角落案例甚至比原始版本更少可读。就个人而言,我不会将此片段复制到生产代码中。
对于生产质量的更新代码,请参阅单独的文章:将字节大小格式化为人类可读格式
复制代码时请包括适当的归属。 有人可能只是打电话给你。 我认为这里的关键外卖是:更喜欢短而简单的循环。 正如您所说,数学很难完全正确(所以,易于错误,难以阅读)。 但我相信它也比基于循环的解决方案慢的数量幅度慢。 一般来说,我同意你的看法。 然而,在这个特殊情况下,应该注意,舍入误差,负输入和浮点精度问题也适用于简单的循环解决方案。 这是非常有趣的文章,我与浮点合作,但总是难以掌握圆角的特殊情况。 本文是良好的学术观点考虑特殊情况。 很好的文章,谢谢分享这个故事! 那么,它在Unix中如何完成? 一些命令像ls,df具有-h人类可读选项。