1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! 特定于平台的装配说明,以避免对具有 FPU 的架构进行中间舍入。
//!

pub use fpu_precision::set_precision;

// 在 x86 上,如果 SSE/SSE2 扩展不可用,则将 x87 FPU 用于浮动操作。
// x87 FPU 默认情况下以 80 位精度运行,这意味着运算将舍入到 80 位,从而在最终将值表示为时将发生双舍入
//
// 32/64 位浮点值。为了克服这个问题,可以设置 FPU 控制字,以便以所需的精度执行计算。
//
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
mod fpu_precision {
    use core::arch::asm;
    use core::mem::size_of;

    /// 一种结构体,用于保留 FPU 控制字的原始值,以便在丢弃该结构体时可以将其恢复。
    ///
    ///
    /// x87 FPU 是一个 16 位寄存器,其字段如下:
    ///
    /// | 12-15 | 10-11 | 8-9 | 6-7 |  5 |  4 |  3 |  2 |  1 |  0 |
    /// |------:|------:|----:|----:|---:|---:|---:|---:|---:|---:|
    /// |       | RC    | PC  |     | PM | UM | OM | ZM | DM | IM |
    ///
    /// IA-32 体系结构软件开发人员手册 (第 1 卷) 中提供了所有字段的文档。
    ///
    /// 与以下代码相关的唯一字段是 PC,Precision Control。
    /// 该字段确定 FPU 执行的操作的精度。
    /// 可以设置为:
    ///  - 0b00,单精度,即 32 位
    ///  - 0b10,双精度,即 64 位
    ///  - 0b11,双精度扩展精度,即 80 位 (默认状态) 0b01 值是保留的,不应使用。
    ///
    pub struct FPUControlWord(u16);

    fn set_cw(cw: u16) {
        // SAFETY: `fldcw` 指令已通过审核,可以与任何 `u16` 一起正常使用
        //
        unsafe {
            asm!(
                "fldcw word ptr [{}]",
                in(reg) &cw,
                options(nostack),
            )
        }
    }

    /// 将 FPU 的 precision 字段设置为 `T` 并返回 `FPUControlWord`。
    pub fn set_precision<T>() -> FPUControlWord {
        let mut cw = 0_u16;

        // 计算适用于 `T` 的 Precision Control 字段的值。
        let cw_precision = match size_of::<T>() {
            4 => 0x0000, // 32 位
            8 => 0x0200, // 64 位
            _ => 0x0300, // 默认为 80 位
        };

        // 丢弃 `FPUControlWord` 结构体时,获取控制字的原始值以在以后还原它
        //
        // SAFETY: `fnstcw` 指令已通过审核,可以与任何 `u16` 一起正常使用
        //
        unsafe {
            asm!(
                "fnstcw word ptr [{}]",
                in(reg) &mut cw,
                options(nostack),
            )
        }

        // 将控制字设置为所需的精度。
        // 这可以通过掩盖旧的精度 (位 8 和 9,0x300) 并将其替换为上面计算的精度标志来实现。
        set_cw((cw & 0xFCFF) | cw_precision);

        FPUControlWord(cw)
    }

    impl Drop for FPUControlWord {
        fn drop(&mut self) {
            set_cw(self.0)
        }
    }
}

// 在大多数体系结构中,浮点运算具有显式的位大小,因此计算的精度取决于每个运算。
//
#[cfg(any(not(target_arch = "x86"), target_feature = "sse2"))]
mod fpu_precision {
    pub fn set_precision<T>() {}
}