【问题标题】:Why does PHP's sprintf not round 5s reliably?为什么 PHP 的 sprintf 不能可靠地循环 5s?
【发布时间】:2014-11-24 14:34:16
【问题描述】:

我依赖于sprintf('%0.1f', 2.25) === '2.3',但事实证明它来自2.2

事实上它似乎是随机的:

php > for ($j=0;$j<=10;$j++) { printf( "%s -> %0.1f\n",$j+ 0.05, $j+0.05); } 
0.05 -> 0.1 // Up, as expected
1.05 -> 1.1 // Up, as expected
2.05 -> 2.0 // Down!
3.05 -> 3.0 // Down!
4.05 -> 4.0 // Down!
5.05 -> 5.0 // Down!
6.05 -> 6.0 // Down!
7.05 -> 7.0 // Down!
8.05 -> 8.1 // Up, as expected
9.05 -> 9.1 // Up, as expected

我完全没有抓住重点吗?我觉得地毯从我身下被拉开,我在学校学到的一切都是错误的......!当然,四舍五入的功能应该始终如一地做到吗? (我注意到round($n, 1) 按预期工作。)

【问题讨论】:

  • 因为 2.05 内部实际上是 2.0499999123423423423 或其他。你正在处理花车。准确性不是他们的强项。
  • Surely a function to round numbers should do it consistently?。如果它实际上是一个四舍五入的函数。但它不是 - sprintf — Return a formatted string,根据 php.net
  • @MarcB 明白了。你怎么知道的?我的意思是你可以手动计算出浮点数的二进制表示,但是如何让 PHP 输出 2.049999123(或任何它)? - 只是为了兴趣!
  • en.wikipedia.org/wiki/Floating_pointen.wikipedia.org/wiki/Double_precision 也是不错的读物。我认为浮点数非常难……如果您在阅读完所有这些内容后没有真正理解它,请不要感到难过。但不足之处在于,其中一些二进制数字是重复数字,而另一些则不是。有点像您不能将分数 1/3 准确地写为没有无限位的小数,但 1/4 很容易。这些重复数字的截断,如果它们恰好发生在您想要四舍五入的位置,可能会导致令人惊讶的结果。

标签: php printf rounding


【解决方案1】:

你应该使用round

http://php.net/manual/en/function.round.php

round(2.25, 2) === floatval('2.3')

【讨论】:

    【解决方案2】:

    总结一下 cmets 在答案中所说的话:

    因为printf 不是舍入函数,而是给出数字的字符串表示的低级函数。在这种情况下,内部数字与设置的数字不同,例如由于浮点内部表示的限制,它可能是2.249999991231231123

    所以printf 将一个与您输入/计算的数字不同的数字四舍五入,在本例中为正确的2.2

    因此,正如另一个答案(以及我最初的问题)指出的那样,更好的解决方案是使用round()(可能在结果上使用sprintf)。

    【讨论】:

      【解决方案3】:

      为了解释为什么round 可以提供比不是专门为舍入设计的函数更好的结果,我们需要考虑双精度浮点表示的限制。双精度数可以表示 15 到 17 个十进制有效数字。来自Wikipedia article on double precision

      如果将最多 15 个有效数字的十进制字符串转换为 IEEE 754 双精度表示,然后再转换回具有相同有效数字的字符串,则最终字符串应与原始字符串匹配。如果将 IEEE 754 双精度转换为具有至少 17 位有效数字的十进制字符串,然后再转换回双精度,则最终数字必须与原始数字匹配

      round 的实现在大多数情况下可以而且应该利用它来“做正确的事”。

      示例 1:

      <?=number_format(2.05,14); //give me 15 significant digits. Guaranteed to produce the orginal number
      

      输出:

      2.05000000000000
      

      示例 2:

      <?=number_format(2.05,16); //give me 17 significant digits. Not guaranteed to produce the orginal number
      

      输出:

      2.0499999999999998
      

      这只是 IEEE 754 行为的演示。

      我猜测(因为我还没有阅读它的实现)sprintf 并没有真正尝试在舍入方面做任何特别聪明的事情,而 round 可能会尝试“正确”舍入(根据IEEE 754)关于您要求的有效位数。

      【讨论】:

      • 很好的解释,谢谢。您认为如果我将问题表述为“为什么 php 的 printf 不是特别聪明”,我可能不会被 -2 击倒? ;-)
      • @artfulrobot 我不确定,因为我没有投反对票。有些人似乎对“幼稚”的解决方案反应消极,尤其是当类似的事情经常出现在 SO 上时。关于 SO 上的浮点有很多问题,我认为长期读者有一定程度的“疲劳”。
      猜你喜欢
      • 2011-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多