PHP简单乘法的四舍五入误差

3

在使用(int)强制转换变量时,PHP似乎会出现错误的四舍五入。为什么会这样呢?

$multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);

输出:1020636。(意外的输出结果

$multiplier = 100000000;
$value = 0.01020637;
echo ($value*$multiplier);

输出结果为:1020637。(期望得到正确的输出结果)

编辑:情况变得更糟了...

$multiplier = 100000000;
$value = 0.01020637;
echo $temp = ($value*$multiplier);
echo '<br/>';
echo (int)$temp;

输出:

1020637

1020636


如果你尝试使用(int)($value*$multiplier + 0.5);会怎样? - CAPS LOCK
1
“永远不要将未知的分数转换为整数,因为这有时会导致意外的结果。”来自PHP手册关于整数。这可能是原因吗? - display-name-is-missing
1
“未知分数”是什么?在我的程序中,($value*multiplier)表达式将始终解析为整数。将其更改为($value*$multiplier)(int)是否有帮助?或者只需使用$temp = ($value*$multiplier); $intVal = (int)$temp)可能会导致预期结果? - bvpx
@bvpx 老实说,我也不知道。但是我找到了一个描述类似于你的问题的链接,看看吧:http://php.net/language.types.float - display-name-is-missing
http://www.php.net/manual/en/language.types.float.php#warn.float-precision - Royal Bg
5个回答

2
处理浮点数时可能会出现问题,浮点数学(以及涉及的问题)是众所周知的,但当你不期而遇时可能会出现问题。似乎在这里就出现了这种情况。您可以广泛阅读规则,或在处理浮点算术时使用语言提供的工具。
当您关心所涉精度时,应使用bcmul()函数。它是一个“可选”扩展,但如果您关心精度,它很快就成为必需品。
例如:
multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);
echo "\n";
echo bcmul($value, $multiplier, 0);

样例:http://ideone.com/Wt9kKb

需要注意的是,bcmath 不是 PHP 核心函数的一部分,也不是所有服务器都安装了它。 - Machavity
有没有一种方法可以在不使用bcmath的情况下完成这个任务?目前我的服务器没有安装它。也许我可以找到一个只包含bcmul函数的库并将其包含在我的项目中? - bvpx
我怀疑你在这里找不到一个很好的用户空间解决方案。我建议你开始礼貌地向系统管理员施压,安装这个扩展。 - preinheimer

1
PHP(特别是32位版本)在处理浮点数时存在问题。这就是为什么将float强制转换为int可能会产生不可预测的结果。有关更多详细信息,请参见PHP Integer页面。基本上,在数学计算中会出现微小的不精确,这可能会在尝试执行像ceil()这样的操作时导致严重问题。
如果您确实需要将数字转换为int,我建议您先对数字进行四舍五入。
$multiplier = 100000000;
$value = 0.01020637;
$temp = round($value*$multiplier);
echo $temp . '<br/>' . (int)$temp;

这通过截断小数点后面的小数错误来实现。虽然bcmath也可以进行截断,但它不是PHP核心的一部分,也不是一个好的整体解决方案。你最好自己编写一个舍入程序,可以返回你想要的精度。在我所在的项目中,我们就是这样做的。我们编写了自己的舍入函数,它可以解决你遇到的问题。如果不知道你想要做什么具体的事情,很难说这是否是你需要的,但这是我们在没有使用bcmath的情况下解决问题的方法。


我有一种直觉,使用 round() 会带来更多问题而不是解决问题... - bvpx
我有点不确定如何回答“我需要多少精度”,但我正在处理的数字将在10000000和0.00000001之间变化,所有这些数字都需要乘以100000000并正确地存储到数据库中。如果必须在服务器上安装bcmath,我不想使用它,但是绝不能将任何不正确的值存储到数据库中,这非常重要。 - bvpx
基本问题是你的数字被存储在内存中,例如1020636.99999992734834。没有其他可用的选项。我仍然认为你可以编写一个函数来实现你想要的功能,但这是你的程序。此外,如果精度很重要,转向使用64位版本的PHP将在第一时间减少错误。 - Machavity
我喜欢编写自己的程序来进行这个计算,因为它只在两三个地方出现,并且每次都是相同的。我假设你编写的程序没有使用 round - 你使用了哪些方法? - bvpx
我们实际上需要一些东西,即使是负数也能正确地向上舍入。它涉及到 ceilfloor,它们是 round 的近亲。不幸的是,我认为这里没有任何你可以应用的东西,否则我会发布它的。 - Machavity
显示剩余2条评论

0
你所遇到的问题是: 当像这样相乘两个数字时:
$mulitply = 0.1 * 100;

你并没有将100和0.1 精确地 相乘,而是与0.09999999998相乘... 而且在使用(int)时,它会将像4.999这样的数字转换为4,因此当使用(int)计算时,你的结果1020636.999999999变成了1020636


当精确度至关重要时,我该如何解决这个问题? - bvpx
@bvpx 我建议使用 BC Math Functions(http://www.php.net/manual/en/ref.bc.php),在你的情况下,你需要特别寻找 bcmul() 函数,或者使用 gmp_mul()(http://www.php.net/manual/en/function.gmp-mul.php)。 - display-name-is-missing
@bvpx 我注意到你询问了一种不需要安装其他东西到 PHP 的解决方案。很抱歉,我不知道 PHP 中是否包含此类函数。 - display-name-is-missing
@bvpx 此外,如果没有必要在服务器端进行此计算,我强烈建议使用纯JavaScript。像这样简单地使用:var multiplied = var_1 * var_2 不会自动给出四舍五入的答案,并且它会给出整个10位小数。 - display-name-is-missing
该应用程序是一个小型的PHP程序,分发到许多计算机并仅在服务器端运行,因此我需要尽可能地使其“裸骨化”,以确保它运行的任何服务器都不会出现没有安装bcmath的问题,同时还能正确存储这些值。 - bvpx

0

bcmul允许更高的精度

$test =  (int) bcmul('100000000', '0.01020637');
echo $test

返回正确答案。


-1

在 PHP 中对浮点数进行四舍五入应使用 round() 函数。仅将其转换为整数无法正确地对值进行四舍五入。

第一个参数是要四舍五入的浮点数(在本例中是计算结果),第二个参数是可选的,指定要返回的小数位数(也称为精度)。还有第三个参数,控制模式。这些可以是 PHP_ROUND_HALF_UP、PHP_ROUND_HALF_DOWN、PHP_ROUND_HALF_EVEN 或 PHP_ROUND_HALF_ODD。

来自 php.net/round 的示例:

<?php
    echo round(3.4);         // 3
    echo round(3.6);         // 4
    echo round(3.6, 0);      // 4
    echo round(1.95583, 2);  // 1.96

    // With the third element, "mode"
    echo round(9.5, 0, PHP_ROUND_HALF_UP);   // 10
    echo round(9.5, 0, PHP_ROUND_HALF_DOWN); // 9
    echo round(9.5, 0, PHP_ROUND_HALF_EVEN); // 10
    echo round(9.5, 0, PHP_ROUND_HALF_ODD);  // 9
?>

以下是一个关于你的代码的示例(实时示例):

<?php
    $multiplier = 100000000;
    $value = 0.01020637;
    echo intval(round($value*$multiplier)); // Returns 1020637
?>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接