PHP精度问题
问题
intval(0.58 * 100)
输出结果为57
分析
要搞明白这个原因, 首先我们要知道浮点数的表示(IEEE 754):
- 浮点数, 以64位的长度(双精度)为例, 会采用1位符号位(E), 11指数位(Q),52位尾数(M)表示(一共64位)。
- 符号位:最高位表示数据的正负,0表示正数,1表示负数。
- 指数位:表示数据以2为底的幂,指数采用偏移码表示。
- 尾数:表示数据小数点后的有效数字。
这里的关键点就在于,小数在二进制的表示,关于小数如何用二进制表示,大家可以百度一下,,我这里就不再赘述, 我们关键的要了解,0.58 对于二进制表示来说,是无限长的值(下面的数字省掉了隐含的1)。
1 | 0.58的二进制表示基本上(52位)是: 0010100011110101110000101000111101011100001010001111 |
而两者的二进制,如果只是通过这52位计算的话,分别是:
1 | 0.58 -> 0.57999999999999996 |
模糊的来看:0.58 * 100 = 57.999999999。
经过intval
,自然就是57了。
可见, 这个问题的关键点就是: “看似有穷的小数, 在计算机的二进制表示里却是无穷的”。
1 | $a = 0.1; |
*PHP浮点型在进行+-%/存在不准确的问题**
解决办法
使用高精度的数学函数
例如使用 bcmul(0.58,100)
输出结果为58。
常用的高精度函数如下
函数 | 说明 |
---|---|
bcadd | 将两个高精度的数字相加 |
bccomp | 比较两个高精度数字,返回-1, 0, 1 |
bcdiv | 将两个高精度数字相除 |
bcmod | 求高精度数字余数 |
bcmul | 将两个高精度数字相乘 |
bcpow | 求高精度数字乘方 |
bcpowmod | 求高精度数字乘方求模,数论里非常常用 |
bcscale | 配置默认小数点位数,相当于就是Linux bc中的”scale=” |
bcsqrt | 求高精度数字平方根 |
bcsub | 将两个高精度数字相减 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 观道君的小站!
评论