从 Rust 和 C++ 设置 FPU 舍入方向没有任何改变

Setting FPU rounding direction from Rust and C++ changes nothing

根据我的理解,将舍入方向设置为 +Inf 将在计算 1/3 时产生 0.333334,而将其设置为 -Inf 时将产生 0.33333

我在 C++ 中尝试使用 fesetround(0x400)fesetround(0x800) 时情况并非如此。我在 Rust 中使用 FFI 从 C.

调用 fsetround 得到相同的行为

C++代码:

#include <cfenv>
#include <iostream>
#include <iomanip>

using namespace std;

int main() {
    double ratio = (double)1/(double)10;
    fesetround(FE_UPWARD);
    cout << fegetround() << " upward " << setprecision(18) <<  ratio << std::endl;
    fesetround(FE_DOWNWARD);
    cout << fegetround() << " downward " << setprecision(18) << ratio << std::endl;
    return 0;
}

(Pastebin)

防锈代码:

extern crate libc;

use libc::c_int;

#[link(name = "rounding")]
extern {
    pub static fe_upward: c_int;
    pub static fe_downward: c_int;
    fn fesetround(rount: c_int) -> c_int;
    fn fegetround() -> c_int;
}

pub fn upward_round() -> i64 {
    unsafe {
        fesetround(fe_upward);
        fegetround() as i64
    }
}

pub fn downward_round() -> i64 {
    unsafe {
        fesetround(fe_downward);
        fegetround() as i64
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_upward() {
        unsafe {
            assert_eq!(fe_upward as i64, upward_round());
        }
    }
    #[test]
    fn test_downward() {
        unsafe {
            assert_eq!(fe_downward as i64, downward_round());
        }
    }
}

(Pastebin)

Starblue 的评论是对的,但让我扩展一下。

"Rounding" 表示在某些未指定的基数中通过一组有限的数字来近似实数。您的示例 1/3 = 0.333333 假设四舍五入为 6 decimal 数字,即基数 10。

但是,计算机以 2 为基数工作。二进制 1/11 是 .1010101010101... 如您所见,四舍五入有点奇怪。如果舍入到最接近 6 位,则为 .101011,如果将其舍入为 7 位,则为 .1010100 - 最后一位始终与倒数第二位相同,这是因为位交替.

当然,向上舍入和向下舍入更简单。向下舍入 .10101010... 只是将结果截断为 N 位:0.101010。而四舍五入只是在最后一位加 1。

现在你用二进制四舍五入,但你用十进制打印结果。这意味着这些模式根本不明显。

这就是事情变得复杂的地方”:在 FP 函数中几乎所有地方都需要舍入,因此它应该很快。这意味着您需要编译舍入模式。但是您不能在每次调用时都重新编译代码到 fesetround。这意味着需要妥协,妥协是 #pragma STDC FENV_ACCESS。如果它是 ON,你会得到慢代码并且 fesetround 工作。如果它关闭(默认),fesetround未指定 结果。