PHP 舍入误差

发布于 2024-10-16 03:20:07 字数 675 浏览 2 评论 0原文

我在我的 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,因为它是共享服务器。

I'm using PHP 5.2.13 on my linux server. I'm getting weird error when rounding numbers. This is my test case:

<?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";

This is results:

        1.51
       11.51
      111.51
     1111.51
    11111.51
   111111.51
  1111111.5
 11111111.51
111111111.51

Anyone knows what causes this? I can't update PHP, since it's shared server.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

我爱人 2024-10-23 03:20:07

这是因为数字 1111111.505 无法用浮点表示法精确表示。它能得到的最接近的是 1111111.5049999999。所以最终发生的事情是,它将代码中的数字转换为 1111111.50499999999,然后进行舍入。结果为 1111111.5。浮点数的问题在于它们无法完全准确地表示许多甚至看似简单的十进制数。例如。数字 0.1 无法使用二进制浮点数精确表示。使用 Python,如果输入 0.1,它将返回 0.10000000000001。加或减几个零。这就是一些语言(例如.Net)提供“小数”数据类型的原因,它能够表示一定范围和小数位数内的所有小数值。小数数据类型的缺点是速度较慢,并且每个数字需要更多的空间来存储。

This is because the number 1111111.505 can't be represented exactly in floating point notation. The closest it can get is 1111111.5049999999. So what ends up happening is that it converts the number in your code to 1111111.50499999999 and then it does the rounding. Which results in 1111111.5. Floating point numbers have problems in that they can't represent a lot of even seemingly simple decimal numbers with complete accuracy. For instance. the number 0.1 cannot be accurately represented using binary floating numbers. Using Python, if you type in 0.1, it returns 0.10000000000001. Plus or minus a few zeros. This is the reason that some languages such as .Net provide a "Decimal" data type which is able to represent all decimal values within a certain range and number of decimal places. The downside of the decimal data type is that it is slower, and each number takes more space to store.

俯瞰星空 2024-10-23 03:20:07

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

它与 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 时如何解释它们。它们的存储方式有很多标准。

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

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

如果 0.01 以二进制形式保留为 01000111101011100001010 的简化和系统截断形式,当它转换回十进制时,读起来就像 0 .0099999...取决于在系统上(64 位计算机将为您提供比 32 位计算机更好的精度)。在这种情况下,操作系统决定是否按照它所看到的方式打印它,或者如何以更易于人类阅读的方式打印它。因此,他们想要如何表示它取决于机器。但可以通过不同的方法在语言层面上对其进行保护。

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

这是因为在这种情况下,您指示应该如何读取它以及您需要多精确。
参考文献:1,< a href="http://www.ima.umn.edu/~arnold//disasters/Patriot-dharan-skeel-siam.pdf" rel="noreferrer">2,3,4,5

If still somebody reach this page with similar problems where floating number subtraction causes error or strange values.
I want to explain this problem with a bit more details. The culprit is the floating point numbers.
To illustrate the problem, I will explain why with a simple example and you can assume from there why it affecting rounding etc.

It is not directly related to PHP and it is not a bug.
However, every programmer should be aware of this issue.

This problem even took many lives two decades ago.

On 25 February 1991 this problem in floating number calculation in a MIM-104 Patriot missile battery prevented it intercepting an incoming Scud missile in Dhahran, Saudi Arabia, contributing to the death of 28 soldiers from the U.S. Army's 14th Quartermaster Detachment.

But why it happens?

The reason is that floating point values represent a limited precision. So, a value might
not have the same string representation after any processing. It also
includes writing a floating point value in your script and directly
printing it without any mathematical operations.

Just a simple example:

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

You would expect it to print 0.01, right?
But it will print a very strange answer like 0.009999999999998

Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.

Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....

Decimal numbers are not well represented in binary due to lack of enough space. So, uou can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.

If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary,when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.

If you format the result, echo number_format(0.009999999999998, 2); it will print 0.01.

It is because in this case you instruct how it should be read and how precision you require.
References: 1,2,3,4,5

腹黑女流氓 2024-10-23 03:20:07

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

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

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

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

You need tp upgrade the wetwaer - not the PHP version.

echo " " . round(1.505, 2) . "\n";

You seem to think you're asking PHP to return the rounded value of 1.505 - but its actually got to return the decimal version of the rounded version of the binary version of 1.505

Try entering the numbers here and have a look at the binary representations.

埖埖迣鎅 2024-10-23 03:20:07

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

如果您确实关心任意浮点数的精度,请使用 bcmath 或 < a href="http://www.php.net/manual/en/book.gmp.php" rel="nofollow">gmp 如果可用,但如果使用 bcmath,则需要创建一个 bround() 函数。我发现的唯一一个真正有效的方法发布在 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);
}

Seems to me like you're running into an accuracy problem caused by using floats. Floats are not guaranteed to be completely accurate and you may find that this problem manifests on one system but not on another.

If you really care about precision with arbitrary floating-point numbers, use bcmath or gmp if one is available, though if using bcmath, you'll need to make a bcround() function. The only one I've found which actually works is posted in the comments of the php.net bcscale page:

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);
}
温暖的光 2024-10-23 03:20:07

@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;
}

@Shabbyrobe: your function is wrong: try to round this number with a scale of 2: 44069.3445

it should be 44069.35, but default php round()- and your Function returns 44069.34

Working code by a member of 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;
}
美人骨 2024-10-23 03:20:07

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);
}

The code in the answer of Shabbyrobe does not handle negative numbers.
This is a working improvement of the code, which handles negative numbers as well as the default scale:

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文