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>() {}
}