我可以依靠 PHP php.ini 精度解决方法来解决浮点问题吗
我在 PHP 中找到了一些 浮点问题的解决方法:
php.ini 设置 precision = 14
342349.23 - 341765.07 = 584.15999999992//浮点问题
php.ini 设置,比如说 precision = 8
342349.23 - 341765.07 = 584.16//瞧!
演示:http://codepad.org/r7o086sS
这有多糟糕?
1.如果我只需要精确的 2 位数计算(钱),我可以依靠这个解决方案吗?
<强>2.如果不能,当这个解决方案失败时,你能给我一个明确的例子吗?
3. 哪个 php.ini.precision 值最适合两位数,金钱计算
<小时>- 请注意,我不能使用整数计算(float*100 = cents),太晚了.
- 我不会处理大于 10^6 的数字
- 我不需要比较数字
更新
@Baba 的答案很好,但他在测试中使用了 precision=20
, precision=6
...所以我仍然不确定它是否会起作用或不是.
请考虑以下事项:
假设 precision = 8
而我唯一要做的就是加法 +
和减法 -
A + B = C
A - B = C
问题 1: 对于 0..999999.99 之间的数字(其中 A 和 B 是带小数位的数字),精确解决方法会失败吗?如果是这样,请给我一个例子.
简单的测试就可以了:
//如果失败了,如果我使用 9,10,11 怎么办???//**失败时如何查找???**ini_set('精度', 8);for($a=0;$a<999999.99;$a+=0.01) {for($b=0;$b<999999.99;$b+=0.01) {//请注意我不需要测试比较 (round($a-$b,2) == ($a-$b))echo ($a + $b).','.($a - $b)." vs ";echo round($a + $b, 2).','.round($a - $b, 2)."
";}}
但显然 99999999 * 2
工作量太大,所以我无法运行本次测试
问题 2: 精确变通方法失败时如何估计/计算?没有如此疯狂的测试?有没有数学*的直接答案?如何计算会失败?
*我不需要知道浮点计算是否有效,但是如果您知道精度以及 A 和 B 的范围,解决方法失败时
<小时>请注意我真的知道 cents 和 bcmath 是最好的解决方案.但我仍然不确定减法和加法的解决方法是否会失败
解决方案简介
浮点运算被许多人认为是一门深奥的学科.这是相当令人惊讶的,因为浮点在计算机系统中无处不在.大多数小数没有作为二进制分数的精确表示,因此需要进行一些舍入.一个好的开始是每个计算机科学家都应该知道的关于浮点运算的知识
问题
问题 1
<块引用>如果我只需要精确的 2 位数计算(钱),我可以依靠这个解决方案吗?
回答 1
如果您需要精确的 2 位,那么答案是 NO 您不能使用 php 精度设置来确定 2 位 小数即使您不打算处理高于 10^6 的数字
,也始终如此.
在计算过程中,如果长度小于8,有可能增加精度长度
问题 2
<块引用>如果不能,当这个解决方案失败时,你能提供一个明确的例子吗?
回答 2
ini_set('precision', 8);//你的精度$a = 5.88 ;//1公斤的成本$q = 2.49 ;//用户购买 2.49 公斤$b = $a * 0.01 ;//10% 折扣仅在第一公斤;回声 ($a * $q) - $b;
输出
14.5824 <---- 不精确的 2 位计算,即使精度为 8
问题 3
<块引用>哪个 php.ini.precision 值最适合两位数,货币计算?
答案 3
精度和金钱计算是两件不同的事情......使用 PHP 精度作为财务计算或浮点长度的基础并不是一个好主意
简单测试
避免使用 bcmath
、 number_format
和简单的 minus
基础
$a = 342349.23;$b = 341765.07;
示例A
ini_set('precision', 20);//设置为20回声 $a - $b, PHP_EOL;echo floatval(round($a - $b, 2)), PHP_EOL;echo number_format($a - $b, 2), PHP_EOL;回声 bcsub($a, $b, 2), PHP_EOL;
输出
584.15999999997438863584.15999999999996817 <----- 开派对584.16584.15 <-------- 这里是 15 因为精度值是 20
示例 B
ini_set('precision', 14);//改为 14回声 $a - $b, PHP_EOL;echo floatval(round($a - $b, 2)), PHP_EOL;echo number_format($a - $b, 2), PHP_EOL;回声 bcsub($a, $b, 2), PHP_EOL;
输出
584.15999999997584.16584.16584.16 <-------- 在 14 变为 16
示例 C
ini_set('precision', 6);//改为 6回声 $a - $b, PHP_EOL;echo floatval(round($a - $b, 2)), PHP_EOL;echo number_format($a - $b, 2), PHP_EOL;回声 bcsub($a, $b, 2), PHP_EOL;
输出
584.16584.16584.16584.00 <--- 在 6 变为 00
示例 D
ini_set('precision', 3);//改为 3回声 $a - $b, PHP_EOL;echo floatval(round($a - $b, 2)), PHP_EOL;echo number_format($a - $b, 2), PHP_EOL;回声 bcsub($a, $b, 2), PHP_EOL;
输出
584584584.16 <-------------------------------- 他们只有一致的值0.00 <--- 在 3 .. 一切都消失了
结论
忘记浮点数,只计算 cents
然后除以 100
如果为时已晚,只需简单地使用 number_format
它看起来一致对我来说.
更新
<块引用>问题 1:对于 0..999999.99 之间的数字(其中 A 和 B 是带小数位的数字),精确解决方法会失败吗?如果是这样,请给我一个例子
Form 0
到 999999.99
以 0.01
为增量大约是 99,999,999
循环的组合可能性是9,999,999,800,000,000
我真的认为没有人愿意为你运行这样的测试.
由于浮点数是具有有限精度的二进制数,因此尝试设置 precision
对确保准确性的效果有限这是一个简单的测试:
ini_set('precision', 8);$a = 0.19;$b = 0.16;$c = 0.01;$d = 0.01;$e = 0.01;$f = 0.01;$g = 0.01;$h = $a + $b + $c + $d + $e + $f + $g;echo "总计:" , $h , PHP_EOL;$i = $h-$a;$i = $i-$b;$i = $i-$c;$i = $i-$d;$i = $i-$e;$i = $i-$f;$i = $i-$g;回声 $i , PHP_EOL;
输出
总计:0.41.0408341E-17 <--- 我相信你会在这里期望 0.00 ;
试试
echo round($i,2) , PHP_EOL;echo number_format($i,2) , PHP_EOL;
输出
<代码>00.00 <-------- 仍然确认 number_format 最准确地保持 2 位数
<块引用>
问题 2:当精确解决方法失败时如何估计/计算?没有如此疯狂的测试?有没有数学*的直接答案?如何计算会不会失败?
事实仍然是 浮点 有 精度问题,但您可以查看数学解决方案
- 机器精度和后向误差分析
- 最小化准确性问题的影响
我不需要知道浮点计算是否有效,但是如果您知道精度以及 A 和 B 的范围,当变通方法失败时
不知道那句话是什么意思:)
I've found some workaround for floating point problem in PHP:
php.ini setting precision = 14
342349.23 - 341765.07 = 584.15999999992 // floating point problem
php.ini setting, let's say precision = 8
342349.23 - 341765.07 = 584.16 // voila!
Demo: http://codepad.org/r7o086sS
How bad is that?
1. Can I rely on this solution if I need just precise 2 digits calculations (money)?
2. If not can you provide me a clear example when this solutions fails?
Edit: 3. Which php.ini.precision value suits best two digits, money calculations
- Please mind I can't use integer calculations (float*100 = cents), it's far too late for that.
- I am not going to work on numbers higher than 10^6
- I don't need to compare numbers
UPDATE
@Baba answer is good, but he used precision=20
, precision=6
in his tests... So still i am not sure is it gonna work or not.
Please consider following:
Let's say precision = 8
and only thing I do is addition +
and subtraction -
A + B = C
A - B = C
Question 1: Is precision workaround gonna fail for numbers between 0..999999.99, where A and B is a number with decimal places? If so please provide me an example.
Simple test would do the job:
// if it fails what if I use 9,10,11 ???
// **how to find when it fails??? **
ini_set('precision', 8);
for($a=0;$a<999999.99;$a+=0.01) {
for($b=0;$b<999999.99;$b+=0.01) {
// mind I don't need to test comparision (round($a-$b,2) == ($a-$b))
echo ($a + $b).','.($a - $b)." vs ";
echo round($a + $b, 2).','.round($a - $b, 2)."
";
}
}
but obviously 99999999 * 2
is too big job so I can't run this test
Question 2: How to estimate/calculate when precision workaround fails? Without such crazy tests? Is there any mathematicial*, straight answer for it? How to calculate is gonna to fail or not?
*i don't need to know floating point calculations works, but when workaround fails if you know precision, and range of A and B
Please mind I really know cents and bcmath are best solution. But still I am not sure is workaround gonna fails or not for substraction and addition
解决方案Introduction
Floating-point arithmetic is considered an esoteric subject by many people. This is rather surprising because floating-point is ubiquitous in computer systems. Most fractional numbers don't have an exact representation as a binary fraction, so there is some rounding going on. A good start is What Every Computer Scientist Should Know About Floating-Point Arithmetic
Questions
Question 1
Can I rely on this solution if I need just precise 2 digits calculations (money)?
Answer 1
If you need need precise 2 digits then the answer is NO you can not use the php precision settings to ascertain a 2 digit decimal all the time even if you are not going to work on numbers higher than 10^6
.
During calculations there is possibility that the precision length can be increased if the length is less than 8
Question 2
If not can you provide me a clear example when this solutions fails?
Answer 2
ini_set('precision', 8); // your precision
$a = 5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;
Output
14.5824 <---- not precise 2 digits calculations even if precision is 8
Question 3
Which php.ini.precision value suits best two digits, money calculations?
Answer 3
Precision and Money calculation are 2 different things ... it's not a good idea to use PHP precision for as a base for your financial calculations or floating point length
Simple Test
Lest Run some example together using bcmath
, number_format
and simple minus
Base
$a = 342349.23;
$b = 341765.07;
Example A
ini_set('precision', 20); // set to 20
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;
Output
584.15999999997438863
584.15999999999996817 <----- Round having a party
584.16
584.15 <-------- here is 15 because precision value is 20
Example B
ini_set('precision', 14); // change to 14
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;
Output
584.15999999997
584.16
584.16
584.16 <-------- at 14 it changed to 16
Example C
ini_set('precision', 6); // change to 6
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;
Output
584.16
584.16
584.16
584.00 <--- at 6 it changed to 00
Example D
ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;
Output
584
584
584.16 <-------------------------------- They only consistent value
0.00 <--- at 3 .. everything is gone
Conclusion
Forget about floating point and just calculate in cents
then later divided by 100
if that is too late just simply use number_format
it looks consistent to me .
Update
Question 1: Is precision workaround gonna fail for numbers between 0..999999.99, where A and B is a number with decimal places? If so please provide me an example
Form 0
to 999999.99
at increment of of 0.01
is about 99,999,999
the combination possibility of your loop is 9,999,999,800,000,000
I really don't think anyone would want to run such test for you.
Since floating point are binary numbers with finite precision trying to set precision
would have limited effect to ensure accuracy Here is a simple test :
ini_set('precision', 8);
$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;
$h = $a + $b + $c + $d + $e + $f + $g;
echo "Total: " , $h , PHP_EOL;
$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;
echo $i , PHP_EOL;
Output
Total: 0.4
1.0408341E-17 <--- am sure you would expect 0.00 here ;
Try
echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;
Output
0
0.00 <------ still confirms number_format is most accurate to maintain 2 digit
Question 2: How to estimate/calculate when precision workaround fails? Without such crazy tests? Is there any mathematical*, straight answer for it? How to calculate is gonna to fail or not?
The fact sill remains Floating Point have Accuracy Problems but for mathematical solutions you can look at
- Machine precision and backward error analysis
- Minimizing the effect of accuracy problems
i don't need to know floating point calculations works, but when workaround fails if you know precision, and range of A and B
Not sure what that statement means :)
相关文章