Skip to content

Commit

Permalink
Get all conversions working
Browse files Browse the repository at this point in the history
  • Loading branch information
AldaronLau committed Feb 9, 2025
1 parent 7d1536e commit dcbe744
Showing 1 changed file with 108 additions and 67 deletions.
175 changes: 108 additions & 67 deletions src/tree/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ fn nonzero_u32_to_f32(fraction: u32) -> f32 {
* f32::from_bits(0b111111100000000000000000000001)
}

/// Convert normal [`f32`] (ranged 0 to 1) to [`u32`] fraction.
#[inline(always)]
fn normal_f32_to_u32(float: f32) -> u32 {

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, stable)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, nightly)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, beta)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, beta)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, nightly)

function `normal_f32_to_u32` is never used

Check warning on line 30 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, stable)

function `normal_f32_to_u32` is never used
// Scale down (f32 max fraction is 2³², and we want 2³² - 1)
let float =
(float * f32::from_bits(0b111111011111111111111111111111)).to_bits();
// Convert fraction to 23 bits
let fraction = (float << 9) >> 1;
// Artificially extend fraction precision, and add inferred 1
let fraction = (1 << 31) | fraction | (fraction >> 23);
// Extract -127 bias 8-bit negative exponent
let exponent = reinterpret_unsigned(127 - reinterpret_signed(float >> 23));
// Scale by exponent
let (fraction, overflow) = fraction.overflowing_shr(exponent - 1);
// Check if fraction should be 0 or not
let nonzero = reinterpret_unsigned(-i32::from(!overflow));

// Make zero if zero, otherwise no-op
fraction & nonzero
}

/// Convert [`u32`] fraction to [`f32`] (ranged 0 to 1).
pub(crate) fn u32_to_f32(fraction: u32) -> f32 {

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, stable)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, nightly)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, beta)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, beta)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, nightly)

function `u32_to_f32` is never used

Check warning on line 50 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, stable)

function `u32_to_f32` is never used
// Check if fraction is 0 or not
Expand All @@ -46,35 +67,34 @@ pub(crate) fn i32_to_f32(int: i32) -> f32 {
nonzero_u32_to_f32(uint).copysign(sign.into())
}

/// Convert floating point (-1 to 1) to signed 32-bit integer
pub(crate) fn float_to_int(float: f32) -> i32 {
// Infinity, 0, subnormal and NaN are all treated as 0
let float = if !float.is_normal() {
0u32
} else {
float.clamp(-1.0, 1.0).to_bits()
};
// Check if float is negative
let is_negative = (float & (1 << 31)) != 0;
// Convert to 23-bit fixed point fraction
let int = (float << 9) >> 9;
// Add floating point inferred digit 1.x, bringing to 24 bits
let int = int | (1 << 23);
// Extract 8-bit exponent
let exponent = (float << 1) >> 24;
// Calculate positive and negative shift
let shift = 120u32.saturating_sub(exponent);
let shift_back = exponent.saturating_sub(119);
// Apply calculated exponential shift
let int = (int << shift_back).saturating_sub(1);
let int = if shift >= 32 { 0 } else { int >> shift };
let int = reinterpret_signed(int);

if is_negative {
-1 - int
} else {
int
}
/// Convert [`f32`] (ranged 0 to 1) to [`u32`] fraction.
#[inline(always)]
fn f32_to_u32(float: f32) -> u32 {

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, stable)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, nightly)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, beta)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, beta)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, nightly)

function `f32_to_u32` is never used

Check warning on line 72 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, stable)

function `f32_to_u32` is never used
// Check if fraction is normal or not
let normal = reinterpret_unsigned(-i32::from(float.is_normal()));
// Flush subnormals, infinity and NaN to zero, and clamp from 0 to 1
let float = f32::from_bits(float.to_bits() & normal).clamp(0.0, 1.0);

// Convert to unsigned integer
normal_f32_to_u32(float)
}

/// Convert [`f32`] (ranged -1 to 1) to [`i32`] fraction.
#[inline(always)]
fn f32_to_i32(float: f32) -> i32 {

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, stable)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, nightly)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest, beta)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, beta)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, nightly)

function `f32_to_i32` is never used

Check warning on line 84 in src/tree/conversions.rs

View workflow job for this annotation

GitHub Actions / test (windows-latest, stable)

function `f32_to_i32` is never used
// Check if fraction is normal or not
let normal = reinterpret_unsigned(-i32::from(float.is_normal()));
// Flush subnormals, infinity and NaN to zero, and clamp from -1 to 1
let float = f32::from_bits(float.to_bits() & normal).clamp(-1.0, 1.0);
// Convert to unsigned integer and reduce precision
let magnitude = reinterpret_signed(normal_f32_to_u32(float.abs()) >> 1);
// Get offset
let offset = -i32::from(float.is_sign_negative());
// Get sign
let sign = (offset * 2) + 1;

// Construct fraction with sign, magnitude, and offset
offset + (magnitude * sign)
}

#[cfg(test)]
Expand Down Expand Up @@ -145,43 +165,64 @@ mod tests {
}

#[test]
fn conversions() {
assert_eq!(float_to_int(0.0), 0);
assert_eq!(float_to_int(1.0), i32::MAX);
assert_eq!(float_to_int(0.5), i32::MAX / 2);
assert_eq!(float_to_int(0.25), i32::MAX / 4);
assert_eq!(float_to_int(0.125), i32::MAX / 8);
assert_eq!(float_to_int(0.0625), i32::MAX / 16);
assert_eq!(float_to_int(0.03125), i32::MAX / 32);
assert_eq!(float_to_int(0.015625), i32::MAX / 64);
assert_eq!(float_to_int(0.0078125), i32::MAX / 128);
assert_eq!(float_to_int(0.00390625), i32::MAX / 512); // / 256);
assert_eq!(float_to_int(0.001953125), i32::MAX / 1024); // / 512);
assert_eq!(float_to_int(6.9849193e-9), 7);
assert_eq!(float_to_int(6.0535967e-9), 6);
assert_eq!(float_to_int(5.122274e-09), 5);
assert_eq!(float_to_int(4.1909516e-9), 4);
assert_eq!(float_to_int(3.259629e-9), 3);
assert_eq!(float_to_int(2.3283064e-9), 2);
assert_eq!(float_to_int(1.3969839e-9), 1);
assert_eq!(float_to_int(2.0f32.powf(-31.0)), 0);
assert_eq!(float_to_int(-2.0f32.powf(-31.0)), -1);
assert_eq!(float_to_int(-1.3969839e-9), -2);
assert_eq!(float_to_int(-2.3283064e-9), -3);
assert_eq!(float_to_int(-3.259629e-9), -4);
assert_eq!(float_to_int(-4.1909516e-9), -5);
assert_eq!(float_to_int(-5.122274e-09), -6);
assert_eq!(float_to_int(-6.0535967e-9), -7);
assert_eq!(float_to_int(-6.9849193e-9), -8);
assert_eq!(float_to_int(-0.001953125), i32::MIN / 1024); // / 512);
assert_eq!(float_to_int(-0.00390625), i32::MIN / 512); // / 256);
assert_eq!(float_to_int(-0.0078125), i32::MIN / 128);
assert_eq!(float_to_int(-0.015625), i32::MIN / 64);
assert_eq!(float_to_int(-0.03125), i32::MIN / 32);
assert_eq!(float_to_int(-0.0625), i32::MIN / 16);
assert_eq!(float_to_int(-0.125), i32::MIN / 8);
assert_eq!(float_to_int(-0.25), i32::MIN / 4);
assert_eq!(float_to_int(-0.5), i32::MIN / 2);
assert_eq!(float_to_int(-1.0), i32::MIN);
fn float_to_unsigned() {
for i in 0..10 {
assert_eq!(
f32_to_u32(0.5f32.powi(i.try_into().unwrap())),
u32::MAX / 2u32.pow(i),
);
}

assert_eq!(f32_to_u32(0.0), 0);
assert_eq!(f32_to_u32(2.3283067e-10), 1);
assert_eq!(f32_to_u32(4.6566134e-10), 2);
assert_eq!(f32_to_u32(6.9849204e-10), 3);
assert_eq!(f32_to_u32(9.313227e-10), 4);
assert_eq!(f32_to_u32(1.1641533e-9), 5);
assert_eq!(f32_to_u32(1.3969841e-9), 6);
assert_eq!(f32_to_u32(1.6298147e-9), 7);
assert_eq!(f32_to_u32(1.8626454e-9), 8);
assert_eq!(f32_to_u32(2.095476e-9), 9);
}

#[test]
fn float_to_signed() {
assert_eq!(f32_to_i32(0.0), 0);
assert_eq!(f32_to_i32(1.0), i32::MAX);
assert_eq!(f32_to_i32(0.5), i32::MAX / 2);
assert_eq!(f32_to_i32(0.25), i32::MAX / 4);
assert_eq!(f32_to_i32(0.125), i32::MAX / 8);
assert_eq!(f32_to_i32(0.0625), i32::MAX / 16);
assert_eq!(f32_to_i32(0.03125), i32::MAX / 32);
assert_eq!(f32_to_i32(0.015625), i32::MAX / 64);
assert_eq!(f32_to_i32(0.0078125), i32::MAX / 128);
assert_eq!(f32_to_i32(0.00390625), i32::MAX / 256);
assert_eq!(f32_to_i32(0.001953125), i32::MAX / 512);
assert_eq!(f32_to_i32(3.49246e-9), 7);
assert_eq!(f32_to_i32(3.0267988e-9), 6);
assert_eq!(f32_to_i32(2.5611373e-9), 5);
assert_eq!(f32_to_i32(2.095476e-9), 4);
assert_eq!(f32_to_i32(1.6298147e-9), 3);
assert_eq!(f32_to_i32(1.1641533e-9), 2);
assert_eq!(f32_to_i32(6.9849204e-10), 1);
assert_eq!(f32_to_i32(2.3283067e-10), 0);
assert_eq!(f32_to_i32(-2.3283067e-10), -1);
assert_eq!(f32_to_i32(-6.9849204e-10), -2);
assert_eq!(f32_to_i32(-1.1641533e-9), -3);
assert_eq!(f32_to_i32(-1.6298147e-9), -4);
assert_eq!(f32_to_i32(-2.095476e-9), -5);
assert_eq!(f32_to_i32(-2.5611373e-9), -6);
assert_eq!(f32_to_i32(-3.0267988e-9), -7);
assert_eq!(f32_to_i32(-3.49246e-9), -8);
assert_eq!(f32_to_i32(-0.001953125), i32::MIN / 512);
assert_eq!(f32_to_i32(-0.00390625), i32::MIN / 256);
assert_eq!(f32_to_i32(-0.0078125), i32::MIN / 128);
assert_eq!(f32_to_i32(-0.015625), i32::MIN / 64);
assert_eq!(f32_to_i32(-0.03125), i32::MIN / 32);
assert_eq!(f32_to_i32(-0.0625), i32::MIN / 16);
assert_eq!(f32_to_i32(-0.125), i32::MIN / 8);
assert_eq!(f32_to_i32(-0.25), i32::MIN / 4);
assert_eq!(f32_to_i32(-0.5), i32::MIN / 2);
assert_eq!(f32_to_i32(-1.0), i32::MIN);
}
}

0 comments on commit dcbe744

Please sign in to comment.