Skip to content

PHP 在使用 int 强制转换过程中发生的数据丢失

看以下这段代码

php
<?php
$value         = 646.80 * 100;
$valueForceInt = (int) $value;

// $valueForceInt 的值是 64679

数据转换

这里使用二进制和 IEEE 754 标准解释这个问题

646.80 这个十进制数由两部分组成:整数部分 646 和小数部分 0.80

整数部分转换(646 → 二进制)

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 → 二进制)

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 的二进制表示

合并整数和小数部分

1010000110.1100110011...₂

尾数部分 :取小数点后的52位(不足补零):

010000110110011001100110011001100110011001

实际存储的二进制值(近似值)

符号位  指数位                 尾数部分(52位)
0      10000001000          010000110110011001100110011001100110011001

乘法运算的精度损失

当执行 646.80 * 100 时:

  • 十进制计算646.80 × 100 = 64680
  • 二进制计算 :由于 646.80 存储的是近似值,乘法结果也是近似值

假设 646.80 实际存储值略小于真实值(如 646.799999999999943157 ):

646.799999999999943157 × 100 = 64679.9999999999943157

强制类型转换的截断行为

PHP的 (int) 转换直接截断小数部分:

  • 64679.999999999994315764679 (而非四舍五入的 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...

这就是浮点数运算在二进制层面的精度丢失问题