Rust 使用f64::mul_add时,如何引入额外的舍入错误?

Rust 使用f64::mul_add时,如何引入额外的舍入错误?,rust,double,precision,floating-accuracy,Rust,Double,Precision,Floating Accuracy,根据文档,可用于减少舍入错误的机会: 融合乘加。计算(自身*a)+b,只需一次四舍五入 错误。这将产生更精确的结果和更好的性能 而不是一个单独的乘法运算,然后是一个加法运算 我正在研究一个线性变换库,其中a*b+…非常常见。当我为我的AffineVectorstruct I的点积引入mul\u add时,精度降低了 这是点积法: impl AffineVector { pub fn dot(self, v: AffineVector) -> f64 { self.x

根据文档,可用于减少舍入错误的机会:

融合乘加。计算
(自身*a)+b
,只需一次四舍五入 错误。这将产生更精确的结果和更好的性能 而不是一个单独的乘法运算,然后是一个加法运算

我正在研究一个线性变换库,其中
a*b+…
非常常见。当我为我的
AffineVector
struct I的点积引入
mul\u add
时,精度降低了

这是点积法:

impl AffineVector {
    pub fn dot(self, v: AffineVector) -> f64 {
        self.x * v.x + self.y * v.y + self.z * v.z + self.w * v.w
        //  self.x.mul_add(v.x, self.y.mul_add(v.y, self.z.mul_add(v.z, self.w * v.w)))
    }
}

使用
mul_add
实现且没有其他更改,由于上一次断言中的浮点精度问题,以下测试失败:

#[test]
fn inverse_rotation() {
    // create a rotation matrix for 1 radian about the Z axis
    let rotate = AffineMatrix::new(Primitives::RotationZ(1.));

    // create a matrix that undoes the rotation of 'rotate'
    let revert = rotate.inverse();

    // apply the transformation to the vector <1,0,0>
    let rotated = rotate.apply_vec3(KVector3::i_hat());        

    // assert that the result is <cos(1),sin(1),0>
    let expected = KVector3::new(C, S, 0.0);        
    assert_eq!(rotated, expected);

    // use the 'revert' matrix to undo the rotation
    let returned = revert.apply_vec3(rotated);     

    // assert that the result is back to <1,0,0>   
    assert_eq!(returned, KVector3::i_hat());
}

使用
mul\u add
如何以及为什么会降低精度?我如何有效地使用它?

为什么您认为您“失去”了精度?似乎更可能的是,之前您有两个舍入错误,恰好相互抵消。现在只有一个错误,你可以看到。在这个完全抵消的过程中,涉及到的操作数的舍入错误似乎极不可能。尽管如此,浮点计算是精确的情况很少,而且这个测试似乎不是其中之一。如果您不希望
0.1+0.2
等于
0.3
,那么您不应该期望该测试通过,除非是随机发生的。(如果你真的期望
0.1+0.2==0.3
,你应该先阅读。)假设从左到右计算加法,重新编码改变了求和的顺序,这是改变舍入的高风险。我建议在你进行更复杂的转换时,将其作为一个副本关闭,答案仍然适用。如果你实际上在做一些浮点数学应该是精确的(只有二进制分数,没有舍入),那么这应该是问题所在。
#[test]
fn inverse_rotation() {
    // create a rotation matrix for 1 radian about the Z axis
    let rotate = AffineMatrix::new(Primitives::RotationZ(1.));

    // create a matrix that undoes the rotation of 'rotate'
    let revert = rotate.inverse();

    // apply the transformation to the vector <1,0,0>
    let rotated = rotate.apply_vec3(KVector3::i_hat());        

    // assert that the result is <cos(1),sin(1),0>
    let expected = KVector3::new(C, S, 0.0);        
    assert_eq!(rotated, expected);

    // use the 'revert' matrix to undo the rotation
    let returned = revert.apply_vec3(rotated);     

    // assert that the result is back to <1,0,0>   
    assert_eq!(returned, KVector3::i_hat());
}