From 50cf0c8aee8d80f5170bd2fc8cd2f19f0f21fb1b Mon Sep 17 00:00:00 2001 From: Joshua Simmons Date: Sun, 5 May 2024 16:10:45 +0200 Subject: [PATCH] narcissus-maths: Add safe f32 to i32 conversions Rather than relying on `to_int_unsafe` which isn't valid for values that overflow `i32`, implement an intrinsics based solution which merely returns an implementation defined value for those cases. --- engine/narcissus-maths/src/lib.rs | 58 +++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/engine/narcissus-maths/src/lib.rs b/engine/narcissus-maths/src/lib.rs index 7fd0ef3..45b0541 100644 --- a/engine/narcissus-maths/src/lib.rs +++ b/engine/narcissus-maths/src/lib.rs @@ -192,14 +192,68 @@ pub fn dequantize_unorm_u8(x: u8) -> f32 { x as f32 / 255.0 } -/// Linearly interpolate between `a` and `b` with the control value `t` +/// Linearly interpolate between `a` and `b` with the control value `t`. /// -/// Returns the exact value of `a` when `t == 0.0` and the exact value of `b` when `t == 1.0` +/// Returns the exact value of `a` when `t == 0.0` and the exact value of `b` when `t == 1.0`. #[inline(always)] pub fn lerp(t: f32, a: f32, b: f32) -> f32 { t.mul_add(b, t.mul_add(-a, a)) } +/// Convert the given `f32` value to `i32`, returning an implementation defined +/// value in case of overflow. +/// +/// If the conversion is inexact, a truncated result is returned. That is, it +/// rounds towards zero. +/// +/// # Notes +/// +/// `f32::to_int_unchecked` can lead to UB when converting an `x` that: +/// * Is Inf +/// * Is NaN +/// * Would produce a value that is out of bounds for +/// +/// This function performs the same operation, but returns an implementation +/// defined value for these cases. +#[inline(always)] +pub fn f32_to_i32(x: f32) -> i32 { + #[cfg(not(target_arch = "x86_64"))] + const _: () = panic!("unsupported platform"); + + #[cfg(target_arch = "x86_64")] + unsafe { + let x = core::arch::x86_64::_mm_load_ss(&x); + core::arch::x86_64::_mm_cvtt_ss2si(x) + } +} + +/// Convert the given `f32` value to `i64`, returning an implementation defined +/// value in case of overflow. +/// +/// If the conversion is inexact, a truncated result is returned. That is, it +/// rounds towards zero. +/// +/// # Notes +/// +/// `f32::to_int_unchecked` can lead to UB when converting an `x` that: +/// * Is Inf +/// * Is NaN +/// * Would produce a value that is out of bounds for +/// +/// This function performs the same operation, but returns an implementation +/// defined value for these cases. +#[inline(always)] +pub fn f32_to_i64(x: f32) -> i64 { + #[cfg(not(target_arch = "x86_64"))] + const _: () = panic!("unsupported platform"); + + #[cfg(target_arch = "x86_64")] + unsafe { + let x = core::arch::x86_64::_mm_load_ss(&x); + core::arch::x86_64::_mm_cvttss_si64(x) + } +} + #[macro_export] macro_rules! impl_shared { ($name:ty, $t:ty, $n:expr) => { -- 2.49.0