我有以下功能确定我的销售是否已全额付清。我不记得为什么我这样做了,但它到目前为止一直在工作,我不记得为什么我必须这样做。
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
答案 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
如果您不确定左操作数是否大于正确的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