PHP 在使用 int 强制转换过程中发生的数据丢失
看以下这段代码
PHP
<?php
$value = 646.80 * 100;
$valueForceInt = (int) $value;
// $valueForceInt 的值是 64679数据转换
这里使用二进制和 IEEE 754 标准解释这个问题
646.80 这个十进制数由两部分组成:整数部分 646 和小数部分 0.80。
整数部分转换(646 → 二进制)
Plaintext
646 ÷ 2 = 323 ... 0
323 ÷ 2 = 161 ... 1
161 ÷ 2 = 80 ... 1
80 ÷ 2 = 40 ... 0
40 ÷ 2 = 20 ... 0
20 ÷ 2 = 10 ... 0
10 ÷ 2 = 5 ... 0
5 ÷ 2 = 2 ... 1
2 ÷ 2 = 1 ... 0
1 ÷ 2 = 0 ... 1结果:646₁₀ = 1010000110₂
小数部分转换(0.80 → 二进制)
Plaintext
0.80 × 2 = 1.6 ... 整数部分 1,小数部分 0.6
0.6 × 2 = 1.2 ... 整数部分 1,小数部分 0.2
0.2 × 2 = 0.4 ... 整数部分 0,小数部分 0.4
0.4 × 2 = 0.8 ... 整数部分 0,小数部分 0.8
0.8 × 2 = 1.6 ... 循环开始结果:0.80₁₀ = 0.1100110011...₂(无限循环小数)
IEEE 754双精度浮点数表示
在64位系统中,PHP的float遵循IEEE 754双精度标准:
- 符号位:1位
- 指数位:11位(偏移量1023)
- 尾数位:52位(隐藏位1)
646.80 的二进制表示
合并整数和小数部分:
Plaintext
1010000110.1100110011...₂尾数部分:取小数点后的52位(不足补零):
Plaintext
010000110110011001100110011001100110011001实际存储的二进制值(近似值)
Plaintext
符号位 指数位 尾数部分(52位)
0 10000001000 010000110110011001100110011001100110011001乘法运算的精度损失
当执行 646.80 * 100 时:
- 十进制计算:
646.80 × 100 = 64680 - 二进制计算:由于
646.80存储的是近似值,乘法结果也是近似值
假设 646.80 实际存储值略小于真实值(如 646.799999999999943157):
Plaintext
646.799999999999943157 × 100 = 64679.9999999999943157强制类型转换的截断行为
PHP的 (int) 转换直接截断小数部分:
64679.9999999999943157→64679(而非四舍五入的64680)
验证代码
PHP
$value = 646.80 * 100;
var_dump($value); // float(64679.999999999)
var_dump((int)$value); // int(64679)为什么 646.80 * 100 不等于 64680?
- 十进制:
646.80 × 100 = 64680(精确) - 二进制:由于
646.80存储为近似值,乘法结果也是近似值64679.999999999...
这就是浮点数运算在二进制层面的精度丢失问题
