你必须知道的——double转换int的问题

问题

问题出自一个价格的转换需要将double类型转换(元)为integer类型(单位分)的数.

$salePrice = 10.2;
$price =(int) ($salePrice  * 100); // 1019

虽然少了一分钱,但是对于这种问题的严重性,每个用户少付款一分钱,虽然说平台用户少,但对于对账会触发报警,甚至不能下单因为实际付款额与售价不等.

接下来我们先从计算机如何存储二进制数字说起.

二进制数字与十进制数字转换

十进制转二进制

整数部分 --> 转为二进制
小数点不动
小数部分 --> 转为二进制

这里重点说下:小数部分转换

0.25转换为二进制如下:

0.25 * 2 = 0.5   // 0 取整数位如果为1取1,这里为0取0,小数部分不为0所以 拿0.5去下一步
0.5*2     =1.0     // 1 小数部分为0 结束计算

10.25就可以表示为 1010.01

二进制转十进制

小数部分转换:

0.25

0 1

得出小数部分算法

0*2^{-1} +  1*2^{-2}

double类型在内存空间模型

众所周知,php使用C语言实现,浮点类型在zval中是double.

符号位     11位 指数      52位尾数
0      0000 0000 000     000.....000

以10.25为例:

1010.01 => 转换为

1.01001 * 2^{3}
  • 将符号存储至符号位,0整数,1负数
  • 将2的指数加中间数存入指数位:3+127=130
  • 将小数位存储到52位尾数位当中.
  • 整数1不需要存储.

中间数:为了表示负指数,所以提出了一个中间数的概念. 中间数:2^n-1 n为指数位数.
所以double类型的中间数为1023, 而float类型为127.
例如指数为-7那么, 127 + (-7)= 120 -> 01111000,读取时候还得减去127,得出真实值.
中间数可以理解为一个符号位,表示指数的符号.
中间数全为1,尾数位为全为0表示无穷大
中间数全为1,尾数位不全为0表示NaN
中间数全为0,尾数位全为0表示0

因此得到内存模型为:

符号位     11位 指数      52位尾数位
0      1000 0000 011     01001 0......000

从内存模型当中读取数据时:

符号位     11位 指数                              52位尾数位
0      1000 0000 011                            01001 0......000
+     130-127 = 0000 0000 011= 3    
尾数位前补1和. => 1.01001 
将小数点右移指数位个数(减去中间数后如果指数位最低位为1则左移) => 1010.01
按照二进制转十进制规则得出:10.25

解决方案

PHP中可以使用bcmath库,避免精度丢失.

两数相乘可以用bcmul这个函数.

(int)bcmul(10.2, 100) =>  1020  

使用bccomp准确比较两个任意精度类型.

其他语言参照相关的库,C语言中也有bcmath库的实现.

在涉及到跟金额相关的业务时候推荐最好这么做,避免产生类似不安全的问题.

你可能感兴趣的:(开发问题)