浮点舍入错误php ...我怎样才能确保它正常工作?

时间:2014-04-23 21:26:49

标签: php

我有以下功能确定我的销售是否已全额付清。我不记得为什么我这样做了,但它到目前为止一直在工作,我不记得为什么我必须这样做。

function _payments_cover_total()
{
    //get_payments is a list of payment amounts such as:
    //10.20, 10.21, or even 10.1010101101 (10 decimals max) 

    $total_payments = 0;

    foreach($this->sale_lib->get_payments() as $payment)
    {
        $total_payments += $payment['payment_amount'];
    }

    //to_currency_no_money rounds total to 2 decimal places
    if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
    {
        return false;
    }

    return true;
}

我想知道是否存在由于舍入错误导致此函数在不应该返回false时返回false的情况。

我有一个问题的主要部分是:

 > 1e-6

我认为在此之前,但在某些情况下它会导致问题。

> 0

3 个答案:

答案 0 :(得分:2)

我认为您正在执行php floating帮助页面上提到的内容。直接引用它:

  

测试浮点值是否相等,是否为上限   使用由于舍入引起的相对误差。这个值被称为   机器epsilon,或单位舍入,是最小的可接受的   计算差异。

$a and $b are equal to 5 digits of precision.

<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;

if(abs($a-$b) < $epsilon) {
    echo "true";
}
?>

所以在你的情况下:

(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6

由于四舍五入的相对误差不应大于 1e-6 0.000001

如果您不确定左操作数是否大于正确的100%时间,那么您应该添加abs(),例如正确性。

$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
 if(abs($relative_error) > 1e-6){
   return false
 }
 return true;

答案 1 :(得分:0)

$x = (1.333-1.233)-(1.334-1.234);
echo $x;

//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)

if($x === 0){
    echo "|zero";
}
else {
    echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero

//how to get to zero?

if($x > 1e-6){//1e-6 mathematical constant
    echo "|non zero";
}
else {
    echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero

if ($x > 1e-6 )
{
   echo " false";
    //echo "|non zero";
    //return false;
}
else{
    echo " true";//<== this resut
    //echo "|zero";
    //return true;
}
//screen -2.2204460492503E-16|non zero|zero true



printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503

答案 2 :(得分:0)

我很抱歉,但在处理货币时,你不应该像PHPoP那样使用PHP浮动。原因还在于PHP浮动帮助页面:

  

此外,有理数的数字可以完全表示为   基数10中的浮点数,如0.1或0.7,没有   精确表示为基数2中的浮点数,即   内部使用,无论尾数的大小。因此,他们   没有a就无法转换成它们的内部二进制对应物   精度损失小。这可能导致令人困惑的结果:for   例如,floor((0.1 + 0.7)* 10)通常会返回7而不是   预期8,因为内部表示将是类似的   7.9999999999999991118 ....

     

所以永远不要将浮动数字结果信任到最后一位数,而不是   直接比较浮点数是否相等。如果更高   精度是必要的,任意精度数学函数和gmp   功能可用。

请注意,帮助页面明确指出,您无法信任浮动结果到最后一位数,无论多短(逗号后)。< / p>

因此,虽然您的浮点数非常短(逗号后只有2位数),但浮点精度(1e-6)并非真正进入,并且您无法100%信任它们。

由于这是一个金钱问题,为了避免愤怒的客户和指责削减一分钱的诉讼(https://en.wikipedia.org/wiki/Salami_slicing),真正的解决方案是:

1)要么使用PHP BC数学,它使用数字的字符串表示 http://php.net/manual/en/book.bc.php

2)正如IMSoP建议的那样,使用整数并在内部以最小面额(美分,欧元或任何其他方式)存储金额。

第一种解决方案可能会更加资源紧张:我自己也没有使用过BC数学,但是存储字符串并进行任意精度数学运算(在这种情况下可能有点过分)定义更多RAM和CPU比使用整数更强烈。

但是,它可能需要对代码的其他部分进行较少的更改。

第二种解决方案需要更改代表,以便在用户看到金额的任何地方,将它们标准化为美元,美分(或任何有你)。

但是,请注意,在这种情况下,您也会在某些时候出现舍入风险的问题,例如:

float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100; 

你可以重新引入舍入问题,因为你再次浮动以及舍入错误的可能性,所以谨慎行事并确保仅在real_amount上进行计算和舍入,从不在shown_amount