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

探讨了在处理货币运算时,双精度浮点数(double)引起的精度问题,详细解析了二进制与十进制转换原理,及double类型在内存中的存储方式,并提供了使用bcmath库避免精度损失的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题

问题出自一个价格的转换需要将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库的实现.

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

### STM32 中 `double` 到 `int` 的强制类型转换 在STM32编程中,当需要将双精度浮点数(`double`)转换为整型(`int`)时,可以采用简单的强制类型转换方式。然而,在执行这种转换过程中需要注意几个方面以确保程序行为符合预期。 #### 方法一:直接强 最直观的方法就是通过显式的类型转换语法实现: ```c #include <stdio.h> int main(void){ double dValue = 123.456; int iConverted; iConverted = (int)dValue; // 显式类型转换 printf("Original Double Value: %f\n",dValue); printf("Converted Int Value: %d\n",iConverted); return 0; } ``` 这种方法简单易懂,但是只保留了数值的小数部分之前的整数部分,并且对于负数会向零取整[^1]。 #### 方法二:使用标准库函数 为了更精确地控制舍入模式,还可以考虑利用C标准库提供的功能来进行更加精细的操作。例如round()、ceil() 或 floor(): ```c #include <math.h> #include <stdio.h> int main(void){ double dPositive = 123.789, dNegative=-123.789; // 向最近的偶数四舍五入 int iP_rounded=(int)round(dPositive); int iN_rounded=(int)round(dNegative); // 向正无穷方向取整 int iP_ceiled =(int)ceil(dPositive); int iN_ceiled =(int)ceil(dNegative); // 向负无穷方向取整 int iP_floored=(int)floor(dPositive); int iN_floored=(int)floor(dNegative); printf("Rounded Positive:%d Rounded Negative:%d\n",iP_rounded,iN_rounded); printf("Ceil Positive:%d Ceil Negative:%d\n",iP_ceiled ,iN_ceiled ); printf("Floor Positive:%d Floor Negative:%d\n",iP_floored,iN_floored ); return 0; } ``` 这里展示了三种不同的舍入策略——四舍五入(round),向上取整(ceil),向下取整(floor)[^2]。 #### 注意事项 - **溢出风险**:如果原始`double`值超出了目标`int`类型的范围,则会发生未定义的行为。因此建议先检查待转换值是否处于安全范围内再做转换。 - **精度损失**:由于`double`能够表达远大于或小于任何可能存在的`int`的最大绝对值,所以在某些情况下可能会丢失大量有效数字信息。 - **编译器优化影响**:不同版本或者配置下的编译器对待此类转换可能存在差异,特别是在涉及特殊浮点表示形式(如NaNs)的情况下[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值