11

我在我的 linux 服务器上使用 PHP 5.2.13。舍入数字时出现奇怪的错误。这是我的测试用例:

<?php
echo "        " . round(1.505, 2) . "\n";
echo "       " . round(11.505, 2) . "\n";
echo "      " . round(111.505, 2) . "\n";
echo "     " . round(1111.505, 2) . "\n";
echo "    " . round(11111.505, 2) . "\n";
echo "   " . round(111111.505, 2) . "\n";
echo "  " . round(1111111.505, 2) . "\n";
echo " " . round(11111111.505, 2) . "\n";
echo "" . round(111111111.505, 2) . "\n";

这是结果:

        1.51
       11.51
      111.51
     1111.51
    11111.51
   111111.51
  1111111.5
 11111111.51
111111111.51

有谁知道这是什么原因造成的?我无法更新 PHP,因为它是共享服务器。

4

6 回答 6

12

这是因为数字 1111111.505 不能用浮点表示法精确表示。它可以得到的最接近的是 1111111.5049999999。所以最终发生的事情是将代码中的数字转换为 1111111.50499999999,然后进行四舍五入。结果为 1111111.5。浮点数的问题在于它们不能完全准确地表示很多看似简单的十进制数。例如。数字 0.1 无法使用二进制浮点数准确表示。使用 Python,如果你输入 0.1,它会返回 0.10000000000001。加或减几个零。这就是某些语言(例如 .Net)提供“十进制”数据类型的原因,该数据类型能够表示一定范围内的所有十进制值和小数位数。

于 2011-02-07T13:51:04.780 回答
8

如果仍然有人以类似的问题到达此页面,其中浮点数减法会导致错误或奇怪的值。我想用更多细节来解释这个问题。罪魁祸首是浮点数。为了说明这个问题,我将用一个简单的例子来解释为什么,你可以从那里假设它为什么会影响舍入等。

它与 PHP 没有直接关系,也不是错误。但是,每个程序员都应该意识到这个问题。

二十年前,这个问题甚至夺走了许多人​​的生命。

1991 年 2 月 25 日,MIM-104 爱国者导弹连的浮点数计算问题阻止了它在沙特阿拉伯达兰拦截来袭的飞毛腿导弹,导致美国陆军第 14 军需分队的 28 名士兵死亡。

但为什么会发生呢?

原因是浮点值表示有限的精度。因此,经过任何处理后,一个值可能不会具有相同的字符串表示形式。它还包括在脚本中编写一个浮点值并直接打印它而无需任何数学运算。

只是一个简单的例子:

$a = '36';
$b = '-35.99';
echo ($a + $b);

你会期望它打印 0.01,对吧?但它会打印一个非常奇怪的答案,如 0.009999999999998

与其他数字一样,浮点数 double 或 float 以 0 和 1 的字符串形式存储在内存中。浮点数与整数的不同之处在于我们在查看 0 和 1 时如何解释它们。它们的存储方式有很多标准。

浮点数通常作为符号位、指数字段和有效数或尾数从左到右打包到计算机数据中......

由于缺少足够的空间,十进制数不能很好地用二进制表示。所以,你不能把 1/3 完全表达为 0.3333333...,对吧?为什么我们不能将 0.01 表示为二进制浮点数是出于同样的原因。1/100 是 0.00000010100011110101110000 ... 重复 10100011110101110000。

如果 0.01 以二进制的简化和系统截断形式 01000111101011100001010 保存,当它被转换回十进制时,它会被读取为 0.0099999.... 取决于系统(64 位计算机会给你比 32 位更好的精度)。在这种情况下,操作系统决定是否按照它看到的方式打印它,或者如何以更易于阅读的方式进行打印。因此,他们想要如何表示它取决于机器。但是可以用不同的方法在语言级别上进行保护。

如果格式化结果,则回显 number_format(0.009999999999998, 2); 它将打印 0.01。

这是因为在这种情况下,您指示应该如何阅读它以及您需要多高的精度。参考文献:1 , 2 , 3 , 4 , 5

于 2014-12-18T04:42:16.663 回答
3

在我看来,您遇到了使用 floats 引起的准确性问题。浮点数不能保证完全准确,您可能会发现这个问题出现在一个系统上,而在另一个系统上却没有。

如果您真的关心任意浮点数的精度,请使用bcmathgmp(如果可用),但如果使用 bcmath,则需要创建一个 bcround() 函数。我发现的唯一一个真正有效的发布在 php.net bcscale页面的评论中:

function bcround($number, $scale=0) {
        $fix = "5";
        for ($i=0;$i<$scale;$i++) $fix="0$fix";
        $number = bcadd($number, "0.$fix", $scale+1);
        return    bcdiv($number, "1.0",    $scale);
}
于 2011-02-07T13:24:45.970 回答
3

您需要升级wetwaer - 而不是PHP 版本。

回声“”。回合(1.505, 2)。"\n";

您似乎认为您要求 PHP 返回 1.505 的舍入值 - 但实际上它必须返回 1.505 二进制版本的舍入版本的十进制版本

尝试在此处输入数字并查看二进制表示。

于 2011-02-07T13:20:11.817 回答
2

@Shabbyrobe:你的函数是错误的:尝试用 2 的比例四舍五入这个数字:44069.3445

它应该是 44069.35,但默认 php round()- 并且您的函数返回 44069.34

php.net 成员的工作代码:

function mround($number, $precision=0) {

$precision = ($precision == 0 ? 1 : $precision);   
$pow = pow(10, $precision);

$ceil = ceil($number * $pow)/$pow;
$floor = floor($number * $pow)/$pow;

$pow = pow(10, $precision+1);

$diffCeil     = $pow*($ceil-$number);
$diffFloor     = $pow*($number-$floor)+($number < 0 ? -1 : 1);

if($diffCeil >= $diffFloor) return $floor;
else return $ceil;
}
于 2013-10-08T08:59:25.890 回答
1

Shabbyrobe答案中的代码不处理负数。这是代码的工作改进,它处理负数以及默认比例:

function bcround($number, $scale = null) {
    if (is_null($scale)) {
        $scale = bcscale();
    }
    $fix = ($number < 0) ? '-' : '';
    $fix .= '0.' . str_repeat('0', $scale) . '5';
    $number = bcadd($number, $fix, $scale + 1);
    return bcdiv($number, "1.0", $scale);
}
于 2020-07-03T19:13:29.000 回答