2009-05-01 73 views
11

因为PHP中的float数据类型不准确,而且MySQL中的FLOAT占用的空间比INT多(且不准确),所以我总是将价格作为INTs存储,在存储之前将其存储为100,以确保我们有精确的2位小数的精度。不过我相信PHP是行为不端的。示例代码:php intval()和floor()的返回值太低?

echo "<pre>"; 

$price = "1.15"; 
echo "Price = "; 
var_dump($price); 

$price_corrected = $price*100; 
echo "Corrected price = "; 
var_dump($price_corrected); 


$price_int = intval(floor($price_corrected)); 
echo "Integer price = "; 
var_dump($price_int); 

echo "</pre>"; 

产生的输出:

Price = string(4) "1.15" 
Corrected price = float(115) 
Integer price = int(114) 

我很惊讶。当最终的结果是低于预期的1,我期待我的测试的输出看起来更像:

Price = string(4) "1.15" 
Corrected price = float(114.999999999) 
Integer price = int(114) 

这将显示出float类型的不准确性。但为什么楼(115)返回114?

+0

这实际上与PHP没有任何关系,并且与所有计算机如何处理浮点数据有关。如果您之前没有遇到浮点错误,则可能需要阅读该主题。 – jmucchiello 2009-05-01 19:24:40

+0

@jmucchiello,我不同意。我明白计算机如何处理浮点数据,这就是为什么我使用整数数据的原因。这是一个PHP问题,当底层数据显示为114.99999999时,PHP显示115 ... – Josh 2009-05-01 19:29:20

回答

25

试试这个作为一个快速修复:

$price_int = intval(floor($price_corrected + 0.5)); 

您所遇到的问题不是PHP的过错,用实数浮点算术所有的编程语言都有类似的问题。

货币计算的一般经验法则是从不使用浮动(既不在数据库中,也不在脚本中)。通过始终存储美分而不是美元,可以避免各种问题。美分是整数,你可以自由地将它们加在一起,并乘以其他整数。无论何时显示号码,都要确保在最后两位数字前面插入一个点。

你得到114而不是115的原因是floor向下舍入,向着最接近的整数,因此floor(114.999999999)变成114.更有趣的问题是为什么1.15 * 100是114.999999999而不是115.原因因为1.15并不完全是115/100,但它是非常小的,所以如果你乘以100,你会得到一个小于115的小数。

这里有一个更详细的解释是什么echo 1.15 * 100;做:

  • 它分析1.15二进制浮点数。这涉及四舍五入,它发生了一点点缩小以获得最接近1.15的二进制浮点数。为什么你不能得到一个确切的数字(没有舍入误差)是1.15在基数2中有无限数量的数字。
  • 它将100分析为二进制浮点数。这涉及四舍五入,但由于100是一个小整数,舍入误差为零。
  • 它计算前两个数字的乘积。这也涉及到一点四舍五入,以找到最接近的二进制浮点数。在此操作中舍入错误恰好为零。
  • 它将二进制浮点数转换为以点为基数的十进制数,并打印此表示形式。这也涉及一点四舍五入。

为什么PHP打印出令人惊讶的Corrected price = float(115)的原因(共114.999,而不是...)是var_dump不打印的确切人数(!),但它打印四舍五入到n - 2(或n - 1)的位数,其中n位是计算的精度。您可以轻松地验证这一点:

echo 1.15 * 100; # this prints 115 
printf("%.30f", 1.15 * 100); # you 114.999.... 
echo 1.15 * 100 == 115.0 ? "same" : "different"; # this prints `different' 
echo 1.15 * 100 < 115.0 ? "less" : "not-less"; # this prints `less' 

如果要打印的花车,请记住:你不要总是看到所有的数字打印时浮

另请参阅PHP float文档开头附近的大警告。

+0

$ price_int = intval(floor($ price_corrected + 0.5))与$ price_int = intval(round($ price_corrected))相同。 – chaos 2009-05-01 19:24:05

+0

@pts,混沌是正确的,实际上我的原始代码就是这样的。没有不同。我的问题是,为什么PHP *说* $ price_converted是115,当它真的是114.999999? (我明白为什么它是114.99999 ...) – Josh 2009-05-01 19:32:13

+0

@pts,我错了,当我说混沌是正确的,我误解了你的代码。这可能有效。 – Josh 2009-05-01 19:36:22

3

PHP正在根据有效位数进行舍入。它隐藏了错误(在第2行)。当然,当地板出现的时候,它并没有更好的理解,并且一路下降。

4

其他答案已经涵盖了原因和解决问题的一个很好的解决方法,我相信。

瞄准从不同的角度解决了这个问题:

对于存放在MySQL价值,你应该看看DECIMAL type,它可以让你存储的精确值与小数。

3

也许是对这个“问题”的另一种可能的解决方案:

intval(number_format($problematic_float, 0, '', '')); 
0

如前所述,这不是用PHP本身的问题,它更多的是处理分数的问题,不能表示为有限的浮点值因此在四舍五入时导致字符损失。

解决方案是确保当您正在处理浮点值并且您需要保持准确性时 - 使用gmp函数或BC数学函数 - bcpow,bcmul等。并且问题将很容易解决。

例如代替 $ price_corrected = $ price * 100;

使用$ price_corrected = bcmul($ price,100);