pub trait ToInt32 {
fn to_int_32(&self) -> i32;
}
pub trait ToUint32: ToInt32 {
fn to_uint_32(&self) -> u32 {
self.to_int_32().cast_unsigned()
}
}
impl<T> ToUint32 for T where T: ToInt32 {}
impl ToInt32 for f64 {
fn to_int_32(&self) -> i32 {
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
unsafe { f64_to_int32_arm64(*self) }
}
#[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
{
if std::arch::is_aarch64_feature_detected!("jsconv") {
unsafe { f64_to_int32_arm64(*self) }
} else {
f64_to_int32_generic(*self)
}
}
#[cfg(not(target_arch = "aarch64"))]
{
f64_to_int32_generic(*self)
}
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "jsconv")]
unsafe fn f64_to_int32_arm64(number: f64) -> i32 {
if number.is_nan() {
return 0;
}
let ret: i32;
unsafe {
std::arch::asm!(
"fjcvtzs {dst:w}, {src:d}",
src = in(vreg) number,
dst = out(reg) ret,
);
}
ret
}
#[expect(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
#[cfg(any(
test,
not(target_arch = "aarch64"),
all(target_arch = "aarch64", not(target_os = "macos"))
))]
fn f64_to_int32_generic(number: f64) -> i32 {
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000;
const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000;
const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; const SIGNIFICAND_SIZE: i32 = 53;
const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE;
const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1;
fn is_denormal(number: f64) -> bool {
(number.to_bits() & EXPONENT_MASK) == 0
}
fn exponent(number: f64) -> i32 {
if is_denormal(number) {
return DENORMAL_EXPONENT;
}
let d64 = number.to_bits();
let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32;
biased_e - EXPONENT_BIAS
}
fn significand(number: f64) -> u64 {
let d64 = number.to_bits();
let significand = d64 & SIGNIFICAND_MASK;
if is_denormal(number) { significand } else { significand + HIDDEN_BIT }
}
fn sign(number: f64) -> i64 {
if (number.to_bits() & SIGN_MASK) == 0 { 1 } else { -1 }
}
if !number.is_finite() || number == 0.0 {
return 0;
}
if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) {
let i = number as i32;
if f64::from(i) == number {
return i;
}
}
let exponent = exponent(number);
let bits = if exponent < 0 {
if exponent <= -SIGNIFICAND_SIZE {
return 0;
}
significand(number) >> -exponent
} else {
if exponent > 31 {
return 0;
}
(significand(number) << exponent) & 0xFFFF_FFFF
};
(sign(number) * (bits as i64)) as i32
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[expect(clippy::cast_precision_loss)]
fn f64_to_int32_conversion() {
assert_eq!(0.0_f64.to_int_32(), 0);
assert_eq!((-0.0_f64).to_int_32(), 0);
assert_eq!(f64::NAN.to_int_32(), 0);
assert_eq!(f64::INFINITY.to_int_32(), 0);
assert_eq!(f64::NEG_INFINITY.to_int_32(), 0);
assert_eq!(((i64::from(i32::MAX) + 1) as f64).to_int_32(), i32::MIN);
assert_eq!(((i64::from(i32::MIN) - 1) as f64).to_int_32(), i32::MAX);
assert_eq!((9_007_199_254_740_992.0_f64).to_int_32(), 0); assert_eq!((-9_007_199_254_740_992.0_f64).to_int_32(), 0); }
#[test]
fn test_generic_and_arm64_consistency() {
let test_values = [
0.0,
-0.0,
1.0,
-1.0,
42.7,
-42.7,
f64::from(i32::MAX),
f64::from(i32::MIN),
f64::from(i32::MAX) + 1.0,
f64::from(i32::MIN) - 1.0,
9_007_199_254_740_992.0, -9_007_199_254_740_992.0, ];
for &value in &test_values {
let generic_result = f64_to_int32_generic(value);
let trait_result = value.to_int_32();
assert_eq!(
generic_result, trait_result,
"Mismatch for value {value}: generic={generic_result}, trait={trait_result}"
);
}
}
#[test]
fn test_nan_handling() {
assert_eq!(f64::NAN.to_int_32(), 0);
assert_eq!(f64_to_int32_generic(f64::NAN), 0);
#[cfg(target_arch = "aarch64")]
{
unsafe {
assert_eq!(f64_to_int32_arm64(f64::NAN), 0);
}
}
}
#[test]
#[expect(clippy::cast_precision_loss)]
fn f64_to_uint32_conversion() {
assert_eq!(0.0_f64.to_uint_32(), 0);
assert_eq!((-0.0_f64).to_uint_32(), 0);
assert_eq!(f64::NAN.to_uint_32(), 0);
assert_eq!(f64::INFINITY.to_uint_32(), 0);
assert_eq!(f64::NEG_INFINITY.to_uint_32(), 0);
assert_eq!(((i64::from(u32::MAX) + 1) as f64).to_uint_32(), u32::MIN);
assert_eq!(((i64::from(u32::MIN) - 1) as f64).to_uint_32(), u32::MAX);
assert_eq!((9_007_199_254_740_992.0_f64).to_uint_32(), 0); assert_eq!((-9_007_199_254_740_992.0_f64).to_uint_32(), 0); }
}