在不使用 bcmath 系列函数下,为何精准二进制打印出来是失真的,但最后计算结果却是对的, 为何?
(使用的 PHP 版本 5.4、5.6 和 7.1 得到的结果一致 )
以下运算,模拟 $a 为商品价格(带两位小数), $b 为数量(整数)。 困惑的是, 是否这一类的电商运算都采用 bcmath 系列函数来做? MySQL 后端存储为 decimal 类型,这个没疑惑。
先看看结果为:
代码为:
<?php
$a = 1.23;
$b = 100000;
printf("%0.30f", $a);
echo '<br />';
printf("%0.30f", $b);
echo "<hr>";
echo '$a 的数据类型: ';
var_dump($a);echo "<hr>";
echo '$b 的数据类型: ';
var_dump($b);echo "<hr>";
$total = $a * $b;
echo '计算结果: ';
var_dump($total);echo "<hr>";
echo '高精度计算结果: ';
printf("%0.30f", $total);
1
xiaoyanbot OP 难道 * 这个运算符, 自动作了四舍五入的处理了?
|
2
xiaoyanbot OP 又进一步测试,发现当 $b 为 10 的时候, 最终结果是失真的
结果为: ~~~ 1.229999999999999982236431605997 10.000000000000000000000000000000 $a 的数据类型:float(1.23) $b 的数据类型:int(10) 计算结果:float(12.3) 高精度计算结果:12.300000000000000710542735760100 ~~~ 当$b 为 100 的时候,最终结果不失真 结果为: ~~~ 1.229999999999999982236431605997 100.000000000000000000000000000000 $a 的数据类型:float(1.23) $b 的数据类型:int(100) 计算结果:float(123) 高精度计算结果:123.000000000000000000000000000000 ~~~ |
3
WuwuGin 2017-10-06 00:40:25 +08:00
PHP 遵循 IEEE 754 双精度:
浮点数, 以 64 位的双精度, 采用 1 位符号位(E), 11 指数位(Q), 52 位尾数(M)表示(一共 64 位). 符号位:最高位表示数据的正负,0 表示正数,1 表示负数。 指数位:表示数据以 2 为底的幂,指数采用偏移码表示 尾数:表示数据小数点后的有效数字. 再来看看小数用二进制怎么表示: 乘 2 取整,顺序排列,即将小数部分乘以 2,然后取整数部分,剩下的小数部分继续乘以 2,然后取整数部分,剩下的小数部分又乘以 2,一直取到小数部分,但是像 0.57 这样的小数像这样一直乘下去,小数部分不可能为 0.有效位的小数用二进制表示却是无穷的。 0.57 的二进制表示基本上(52 位)是: 0010001111010111000010100011110101110000101000111101 如果只有 52 位的话,0.57 =》 0.56999999999999995 不难看出上面意外的结果了吧。 ref:http://www.jb51.net/article/65984.htm |
4
Sikoay 2017-10-06 00:44:51 +08:00 via Android
难道不是都采用整数来进行运算和存储吗,float 永远无法准确地计算一个数(忘记是不是这样说的了,effective Java 中有写过
|
5
Sikoay 2017-10-06 00:49:35 +08:00 via Android 1
我们是将金额乘以 100 (精确到小数点两位后),然后运算和存储,显示的时候再将计算之后的值除以 100
|
6
eoo 2017-10-06 09:40:27 +08:00 via Android
楼上+1 腾讯也是这么做滴
|
7
xiaoyanbot OP |
8
xiaoyanbot OP 阿里的 Java 手册,关于这一部分的描述
~~~ 6. [强制] 小数类型为 decimal,禁止使用 float 和 double。 说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。 ~~~ |
9
eoo 2017-10-06 11:06:23 +08:00
@xiaoyanbot 其他我不知道 我只知道 我抓 QQ 支付记录包的时候 返回的都是 支付金额 x 100 你想一下 QQ 支付系统都这样做了 你觉得其他的会有什么不一样?
|
11
blackshadow 2017-10-06 16:39:26 +08:00 via Android
楼上说的,支付用金额 x100。是因为人民币最小单位是分,这样计算的金额,利息等比较准确。几乎所有的支付系统都是按分存取的吧。在我碰到的 PHP 支付系统里确实有使用了 bcmath 系统函数。至少我做过的是这样。
|
12
xiaoyanbot OP |