php intval() 和 Floor() 返回值太低?

发布于 2024-07-18 17:49:40 字数 773 浏览 5 评论 0原文

因为 PHP 中的 float 数据类型不准确,并且 MySQL 中的 FLOAT 比 INT 占用更多空间(并且不准确),所以我总是将价格存储为 INT,在存储之前乘以 100,以确保精确到小数点后两位。 但我认为 PHP 行为不当。 示例代码:

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 类型的不准确性。 但为什么 Floor(115) 返回 114 ?

Because the float data type in PHP is inaccurate, and a FLOAT in MySQL takes up more space than an INT (and is inaccurate), I always store prices as INTs, multipling by 100 before storing to ensure we have exactly 2 decimal places of precision. However I believe PHP is misbehaving. Example code:

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>";

Produced output:

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

I was surprised. When the final result was lower than expected by 1, I was expecting the output of my test to look more like:

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

which would demonstrate the inaccuracy of the float type. But why is floor(115) returning 114??

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

把时间冻结 2024-07-25 17:49:40

尝试将此作为快速解决方案:

$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 是一个小整数,所以舍入误差为零。
  • 它计算前两个数字的乘积。 这还涉及一点舍入,以找到最接近的二进制浮点数。 在此操作中,舍入误差恰好为零。
  • 它将二进制浮点数转换为带点的以 10 为基数的十进制数,并打印此表示形式。 这还涉及一点舍入。

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 文档开头附近的大警告。

Try this as a quick fix:

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

The problem you are experiencing is not PHP's fault, all programming languages using real numbers with floating point arithmetics have similar issues.

The general rule of thumb for monetary calculations is to never use floats (neither in the database nor in your script). You can avoid all kinds of problems by always storing the cents instead of dollars. The cents are integers, and you can freely add them together, and multiply by other integers. Whenever you display the number, make sure you insert a dot in front of the last two digits.

The reason why you are getting 114 instead of 115 is that floor rounds down, towards the nearest integer, thus floor(114.999999999) becomes 114. The more interesting question is why 1.15 * 100 is 114.999999999 instead of 115. The reason for that is that 1.15 is not exactly 115/100, but it is a very little less, so if you multiply by 100, you get a number a tiny bit smaller than 115.

Here is a more detailed explanation what echo 1.15 * 100; does:

  • It parses 1.15 to a binary floating point number. This involves rounding, it happens to round down a little bit to get the binary floating point number nearest to 1.15. The reason why you cannot get an exact number (without rounding error) is that 1.15 has infinite number of numerals in base 2.
  • It parses 100 to a binary floating point number. This involves rounding, but since 100 is a small integer, the rounding error is zero.
  • It computes the product of the previous two numbers. This also involves a little rounding, to find the nearest binary floating point number. The rounding error happens to be zero in this operation.
  • It converts the binary floating point number to a base 10 decimal number with a dot, and prints this representation. This also involves a little rounding.

The reason why PHP prints the surprising Corrected price = float(115) (instead of 114.999...) is that var_dump doesn't print the exact number (!), but it prints the number rounded to n - 2 (or n - 1) digits, where n digits is the precision of the calculation. You can easily verify this:

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'

If you are printing floats, remember: you don't always see all digits when you print the float.

See also the big warning near the beginning of the PHP float docs.

尐籹人 2024-07-25 17:49:40

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

为了从不同的角度解决问题:

为了在 MySQL 中存储价格值,您可能应该查看 DECIMAL 类型,它允许您存储带小数位的精确值。

The other answers have covered the cause and a good workaround to the problem, I believe.

To aim at fixing the problem from a different angle:

For storing price values in MySQL, you should probably look at the DECIMAL type, which lets you store exact values with decimal places.

简单 2024-07-25 17:49:40

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

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

Maybe it's another possible solution for this "problem":

intval(number_format($problematic_float, 0, '', ''));
终遇你 2024-07-25 17:49:40

PHP 根据有效数字进行舍入。 它隐藏了不准确性(第 2 行)。 当然,当地板出现时,它不会知道任何更好的情况,并且会一路下降。

PHP is doing rounding based on significant digits. It's hiding the inaccuracy (on line 2). Of course, when floor comes along, it doesn't know any better and lops it all the way down.

剩一世无双 2024-07-25 17:49:40

如前所述,这不是 PHP 本身的问题,更多的是处理无法表示为有限浮点值的分数的问题,因此导致舍入时字符丢失。

解决方案是确保在处理浮点值时需要保持准确性 - 使用 gmp 函数或 BC 数学函数 - bcpow、bcmul 等。 问题就会很容易解决。

例如代替
$price_ Corrected = $价格*100;

使用 $price_ Corrected = bcmul($price,100);

As stated this is not a problem with PHP per se, It is more of an issue of handling fractions that can't be expressed as finite floating point values hence leading to loss of character when rounding up.

The solution is to ensure that when you are working on floating point values and you need to maintain accuracy - use the gmp functions or the BC maths functions - bcpow, bcmul et al. and the problem will be resolved easily.

E.g instead of
$price_corrected = $price*100;

use $price_corrected = bcmul($price,100);

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文