我有三个使用(32位)浮点精确表示的数字:
x = 16277216, y = 16077216, z = -261692320000000
我期望执行融合乘加x*y+z
,以返回数学上正确的值,但四舍五入。正确的数学值是-2489344
,不需要四舍五入,因此这应该是融合乘加的输出。但是当我执行fma(x,y,z)
时,结果是-6280192
。为什么?
我用的是铁锈。注意,z
是-x*y
的四舍五入结果。
let x: f32 = 16277216.0;
let y: f32 = 16077216.0;
let z = - x * y;
assert_eq!(z, -261692320000000.0 as f32); // pass
let result = x.mul_add(y, z);
assert_eq!(result, -2489344.0 as f32); // fail
println!("x: {:>32b}, {}", x.to_bits(), x);
println!("y: {:>32b}, {}", y.to_bits(), y);
println!("z: {:>32b}, {}", z.to_bits(), z);
println!("result: {:>32b}, {}", result.to_bits(), result);
输出为
x: 1001011011110000101111011100000, 16277216
y: 1001011011101010101000110100000, 16077216
z: 11010111011011100000000111111110, -261692320000000
result: 11001010101111111010100000000000, -6280192
我有三个数字,它们使用(32位)浮点进行精确表示:
x = 16277216, y = 16077216, z = -261692320000000
这个前提是错误的-261692320000000无法以任何32位浮点格式精确表示,因为其有效位需要37位才能表示。
通常用于float
的IEEE-754二进制32格式具有24位有效位。将−26169232000000的有效位缩放到224以下,大小为−2616923200000=−15598077.7740478515625•224。正如我们所看到的,有效位在这个尺度上不是整数,所以它不能精确地表示,我也不认为它是精确的。最接近的可表示值为−15598078•224=-26169323790848。
println!("z: {:>32b}, {}", z.to_bits(), z);
…z:111010111011011100000000111111110,261692320000000
Rust在撒谎;则CCD_ 10的值不是CCD_。它可能使用了一些算法,如四舍五入到8位有效数字,其余部分使用零。z
的实际值为−261692323790848。
使用普通实数算术的16277216•16077216−261692323790848的值为−6280192,因此FMA的结果是正确的。
舍入误差出现在let z = - x * y;
中,其中16277216和16077216的乘积将261692317510656的实数算术结果舍入为二进制表示的最接近值322261692323790848。