From f439ca5b0892e50a2a3636790a855be2a4a7f3ef Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 20 Jun 2024 01:27:42 -0500 Subject: [PATCH] Refactor float casting tests This is an attempt to remove the magic from a lot of the numbers tested, which should make things easier when it is time to add `f16` and `f128`. A nice side effect is that these tests now cover all int <-> float conversions with the same amount of tests. Co-authored-by: Ralf Jung --- tests/pass/float.rs | 572 ++++++++++++++++++++++++++++++-------------- 1 file changed, 387 insertions(+), 185 deletions(-) diff --git a/tests/pass/float.rs b/tests/pass/float.rs index 5464627fa1..83a5959898 100644 --- a/tests/pass/float.rs +++ b/tests/pass/float.rs @@ -4,8 +4,11 @@ #![feature(f128)] #![feature(f16)] #![allow(arithmetic_overflow)] +#![allow(internal_features)] -use std::fmt::Debug; +use std::any::type_name; +use std::cmp::min; +use std::fmt::{Debug, Display, LowerHex}; use std::hint::black_box; use std::{f32, f64}; @@ -29,15 +32,39 @@ fn main() { test_algebraic(); } -// Helper function to avoid promotion so that this tests "run-time" casts, not CTFE. -// Doesn't make a big difference when running this in Miri, but it means we can compare this -// with the LLVM backend by running `rustc -Zmir-opt-level=0 -Zsaturating-float-casts`. -#[track_caller] -#[inline(never)] -fn assert_eq(x: T, y: T) { - assert_eq!(x, y); +trait Float: Copy + PartialEq + Debug { + /// The unsigned integer with the same bit width as this float + type Int: Copy + PartialEq + LowerHex + Debug; + const BITS: u32 = size_of::() as u32 * 8; + const EXPONENT_BITS: u32 = Self::BITS - Self::SIGNIFICAND_BITS - 1; + const SIGNIFICAND_BITS: u32; + + /// The saturated (all ones) value of the exponent (infinity representation) + const EXPONENT_SAT: u32 = (1 << Self::EXPONENT_BITS) - 1; + + /// The exponent bias value (max representable positive exponent) + const EXPONENT_BIAS: u32 = Self::EXPONENT_SAT >> 1; + + fn to_bits(self) -> Self::Int; +} + +macro_rules! impl_float { + ($ty:ty, $ity:ty) => { + impl Float for $ty { + type Int = $ity; + // Just get this from std's value, which includes the implicit digit + const SIGNIFICAND_BITS: u32 = <$ty>::MANTISSA_DIGITS - 1; + + fn to_bits(self) -> Self::Int { + self.to_bits() + } + } + }; } +impl_float!(f32, u32); +impl_float!(f64, u64); + trait FloatToInt: Copy { fn cast(self) -> Int; unsafe fn cast_unchecked(self) -> Int; @@ -64,13 +91,53 @@ float_to_int!(f64 => i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); /// Test this cast both via `as` and via `approx_unchecked` (i.e., it must not saturate). #[track_caller] #[inline(never)] -fn test_both_cast(x: F, y: I) +fn test_both_cast(x: F, y: I, msg: impl Display) where F: FloatToInt, I: PartialEq + Debug, { - assert_eq!(x.cast(), y); - assert_eq!(unsafe { x.cast_unchecked() }, y); + let f_tname = type_name::(); + let i_tname = type_name::(); + assert_eq!(x.cast(), y, "{f_tname} -> {i_tname}: {msg}"); + assert_eq!(unsafe { x.cast_unchecked() }, y, "{f_tname} -> {i_tname}: {msg}",); +} + +/// Helper function to avoid promotion so that this tests "run-time" casts, not CTFE. +/// Doesn't make a big difference when running this in Miri, but it means we can compare this +/// with the LLVM backend by running `rustc -Zmir-opt-level=0 -Zsaturating-float-casts`. +#[track_caller] +#[inline(never)] +fn assert_eq(x: T, y: T) { + assert_eq!(x, y); +} + +/// The same as `assert_eq` except prints a specific message on failure +#[track_caller] +#[inline(never)] +fn assert_eq_msg(x: T, y: T, msg: impl Display) { + assert_eq!(x, y, "{msg}"); +} + +/// Check that floats have bitwise equality +fn assert_biteq(a: F, b: F, msg: impl Display) { + let ab = a.to_bits(); + let bb = b.to_bits(); + let tname = type_name::(); + let width = (2 + F::BITS / 4) as usize; + assert_eq_msg::( + ab, + bb, + format_args!("({ab:#0width$x} != {bb:#0width$x}) {tname}: {msg}"), + ); +} + +/// Check that two floats have equality +fn assert_feq(a: F, b: F, msg: impl Display) { + let ab = a.to_bits(); + let bb = b.to_bits(); + let tname = type_name::(); + let width = (2 + F::BITS / 4) as usize; + assert_eq_msg::(a, b, format_args!("({ab:#0width$x} != {bb:#0width$x}) {tname}: {msg}")); } fn basic() { @@ -148,155 +215,314 @@ fn basic() { assert_eq!(34.2f64.abs(), 34.2f64); } +/// Test casts from floats to ints and back +macro_rules! test_ftoi_itof { + ( + f: $fty:ty, + i: $ity:ty, + // Int min and max as float literals + imin_f: $imin_f:literal, + imax_f: $imax_f:literal $(,)? + ) => {{ + /// By default we test float to int `as` casting as well as to_int_unchecked + fn assert_ftoi(f: $fty, i: $ity, msg: &str) { + #[allow(unused_comparisons)] + if <$ity>::MIN >= 0 && f < 0.0 { + // If `ity` is signed and `f` is negative, it is unrepresentable so skip + // unchecked casts. + assert_ftoi_unrep(f, i, msg); + } else { + test_both_cast::<$fty, $ity>(f, i, msg); + } + } + + /// Unrepresentable values only get tested with `as` casting, not unchecked + fn assert_ftoi_unrep(f: $fty, i: $ity, msg: &str) { + assert_eq_msg::<$ity>( + f as $ity, + i, + format_args!("{} -> {}: {msg}", stringify!($fty), stringify!($ity)), + ); + } + + /// Int to float checks + fn assert_itof(i: $ity, f: $fty, msg: &str) { + assert_eq_msg::<$fty>( + i as $fty, + f, + format_args!("{} -> {}: {msg}", stringify!($ity), stringify!($fty)), + ); + } + + /// Check both float to int and int to float + fn assert_bidir(f: $fty, i: $ity, msg: &str) { + assert_ftoi(f, i, msg); + assert_itof(i, f, msg); + } + + /// Check both float to int and int to float for unrepresentable numbers + fn assert_bidir_unrep(f: $fty, i: $ity, msg: &str) { + assert_ftoi_unrep(f, i, msg); + assert_itof(i, f, msg); + } + + let fbits = <$fty>::BITS; + let fsig_bits = <$fty>::SIGNIFICAND_BITS; + let ibits = <$ity>::BITS; + let imax: $ity = <$ity>::MAX; + let imin: $ity = <$ity>::MIN; + let izero: $ity = 0; + #[allow(unused_comparisons)] + let isigned = <$ity>::MIN < 0; + + #[allow(overflowing_literals)] + let imin_f: $fty = $imin_f; + #[allow(overflowing_literals)] + let imax_f: $fty = $imax_f; + + // If an integer can fit entirely in the mantissa (counting the hidden bit), every value + // can be represented exactly. + let all_ints_exact_rep = ibits <= fsig_bits + 1; + + // We can represent the full range of the integer (but possibly not every value) without + // saturating to infinity if `1 << (I::BITS - 1)` (single one in the MSB position) is + // within the float's dynamic range. + let int_range_rep = ibits - 1 < <$fty>::EXPONENT_BIAS; + + // Skip unchecked cast when int min/max would be unrepresentable + let assert_ftoi_big = if all_ints_exact_rep { assert_ftoi } else { assert_ftoi_unrep }; + let assert_bidir_big = if all_ints_exact_rep { assert_bidir } else { assert_bidir_unrep }; + + // Near zero representations + assert_bidir(0.0, 0, "zero"); + assert_ftoi(-0.0, 0, "negative zero"); + assert_ftoi(1.0, 1, "one"); + assert_ftoi(-1.0, izero.saturating_sub(1), "negative one"); + assert_ftoi(1.0 - <$fty>::EPSILON, 0, "1.0 - ε"); + assert_ftoi(1.0 + <$fty>::EPSILON, 1, "1.0 + ε"); + assert_ftoi(-1.0 + <$fty>::EPSILON, 0, "-1.0 + ε"); + assert_ftoi(-1.0 - <$fty>::EPSILON, izero.saturating_sub(1), "-1.0 - ε"); + assert_ftoi(<$fty>::from_bits(0x1), 0, "min subnormal"); + assert_ftoi(<$fty>::from_bits(0x1 | 1 << (fbits - 1)), 0, "min neg subnormal"); + + // Spot checks. Use `saturating_sub` to create negative integers so that unsigned + // integers stay at zero. + assert_ftoi(0.9, 0, "0.9"); + assert_ftoi(-0.9, 0, "-0.9"); + assert_ftoi(1.1, 1, "1.1"); + assert_ftoi(-1.1, izero.saturating_sub(1), "-1.1"); + assert_ftoi(1.9, 1, "1.9"); + assert_ftoi(-1.9, izero.saturating_sub(1), "-1.9"); + assert_ftoi(5.0, 5, "5.0"); + assert_ftoi(-5.0, izero.saturating_sub(5), "-5.0"); + assert_ftoi(5.9, 5, "5.0"); + assert_ftoi(-5.9, izero.saturating_sub(5), "-5.0"); + + // Exercise the middle of the integer's bit range. A power of two fits as long as the + // exponent can fit its log2, so cap at the maximum representable power of two (which + // is the exponent's bias). + let half_i_max: $ity = 1 << min(ibits / 2, <$fty>::EXPONENT_BIAS); + let half_i_min = izero.saturating_sub(half_i_max); + assert_bidir(half_i_max as $fty, half_i_max, "half int max"); + assert_bidir(half_i_min as $fty, half_i_min, "half int min"); + + // Integer limits + assert_bidir_big(imax_f, imax, "i max"); + assert_bidir_big(imin_f, imin, "i min"); + + // We need a small perturbation to test against that does not round up to the next + // integer. `f16` needs a smaller perturbation since it only has resolution for ~1 decimal + // place near 10^3. + let perturb = if fbits < 32 { 0.9 } else { 0.99 }; + assert_ftoi_big(imax_f + perturb, <$ity>::MAX, "slightly above i max"); + assert_ftoi_big(imin_f - perturb, <$ity>::MIN, "slightly below i min"); + + // Tests for when we can represent the integer's magnitude + if int_range_rep { + // If the float can represent values larger than the integer, float extremes + // will saturate. + assert_ftoi_unrep(<$fty>::MAX, imax, "f max"); + assert_ftoi_unrep(<$fty>::MIN, imin, "f min"); + + // Max representable power of 10 + let pow10_max = (10 as $ity).pow(imax.ilog10()); + + // If the power of 10 should be representable (fits in a mantissa), check it + if ibits - pow10_max.leading_zeros() - pow10_max.trailing_zeros() <= fsig_bits + 1 { + assert_bidir(pow10_max as $fty, pow10_max, "pow10 max"); + } + } + + // Test rounding when some integers that do not have an exact representation. + if int_range_rep && !all_ints_exact_rep { + // The maximum representable integer is a saturated mantissa (including the implicit + // bit), shifted into the int's leftmost position. + // + // Positive signed integers never use their top bit, so shift by one bit fewer. + let sat_mantissa: $ity = (1 << (fsig_bits + 1)) - 1; + let adj = if isigned { 1 } else { 0 }; + let max_rep = sat_mantissa << (sat_mantissa.leading_zeros() - adj); + + // This value should roundtrip exactly + assert_bidir(max_rep as $fty, max_rep, "max representable int"); + + // The cutoff for where to round to `imax` is halfway between the maximum exactly + // representable integer and `imax`. This should round down (to `max_rep`). + let max_non_sat = max_rep + ((imax - max_rep) / 2); + assert_bidir(max_non_sat as $fty, max_rep, "max non saturating int"); + + // So the next value up should round up to the maximum value of the integer + assert_bidir_unrep((max_non_sat + 1) as $fty, imax, "min infinite int"); + + if isigned { + // Floats can always represent the minimum signed number if they can fit the + // exponent, because it is just a `1` in the MSB. So, no negative int -> float + // conversion will round to negative infinity (if the exponent fits). + // + // Since `imin` is thus the minimum representable value, we test rounding near + // the next value. This happens to be the opposite of the maximum representable + // value, and it should roundtrip exactly. + let next_min_rep = max_rep.wrapping_neg(); + assert_bidir(next_min_rep as $fty, next_min_rep, "min representable above imin"); + + // Following a similar pattern as for positive numbers, halfway between this value + // and `imin` should round back to `next_min_rep`. + let min_non_sat = imin - ((imin - next_min_rep) / 2) + 1; + assert_bidir( + min_non_sat as $fty, + next_min_rep, + "min int that does not round to imin", + ); + + // And then anything else saturates to the minimum value. + assert_bidir_unrep( + (min_non_sat - 1) as $fty, + imin, + "max negative int that rounds to imin", + ); + } + } + + // Check potentially saturating int ranges. (`imax_f` here will be `$fty::INFINITY` if + // it cannot be represented as a finite value.) + assert_itof(imax, imax_f, "imax"); + assert_itof(imin, imin_f, "imin"); + + // Float limits + assert_ftoi_unrep(<$fty>::INFINITY, imax, "f inf"); + assert_ftoi_unrep(<$fty>::NEG_INFINITY, imin, "f neg inf"); + assert_ftoi_unrep(<$fty>::NAN, 0, "f nan"); + assert_ftoi_unrep(-<$fty>::NAN, 0, "f neg nan"); + }}; +} + +/// Test casts from one float to another +macro_rules! test_ftof { + ( + f1: $f1:ty, + f2: $f2:ty $(,)? + ) => {{ + type F2Int = <$f2 as Float>::Int; + + let f1zero: $f1 = 0.0; + let f2zero: $f2 = 0.0; + let f1five: $f1 = 5.0; + let f2five: $f2 = 5.0; + + assert_biteq((f1zero as $f2), f2zero, "0.0"); + assert_biteq(((-f1zero) as $f2), (-f2zero), "-0.0"); + assert_biteq((f1five as $f2), f2five, "5.0"); + assert_biteq(((-f1five) as $f2), (-f2five), "-5.0"); + + assert_feq(<$f1>::INFINITY as $f2, <$f2>::INFINITY, "max -> inf"); + assert_feq(<$f1>::NEG_INFINITY as $f2, <$f2>::NEG_INFINITY, "max -> inf"); + assert!((<$f1>::NAN as $f2).is_nan(), "{} -> {} nan", stringify!($f1), stringify!($f2)); + + let min_sub_casted = <$f1>::from_bits(0x1) as $f2; + let min_neg_sub_casted = <$f1>::from_bits(0x1 | 1 << (<$f1>::BITS - 1)) as $f2; + + if <$f1>::BITS > <$f2>::BITS { + assert_feq(<$f1>::MAX as $f2, <$f2>::INFINITY, "max -> inf"); + assert_feq(<$f1>::MIN as $f2, <$f2>::NEG_INFINITY, "max -> inf"); + assert_biteq(min_sub_casted, f2zero, "min subnormal -> 0.0"); + assert_biteq(min_neg_sub_casted, -f2zero, "min neg subnormal -> -0.0"); + } else { + // When increasing precision, the minimum subnormal will just roll to the next + // exponent. This exponent will be the current exponent (with bias), plus + // `sig_bits - 1` to account for the implicit change in exponent (since the + // mantissa starts with 0). + let sub_casted = <$f2>::from_bits( + ((<$f2>::EXPONENT_BIAS - (<$f1>::EXPONENT_BIAS + <$f1>::SIGNIFICAND_BITS - 1)) + as F2Int) + << <$f2>::SIGNIFICAND_BITS, + ); + assert_biteq(min_sub_casted, sub_casted, "min subnormal"); + assert_biteq(min_neg_sub_casted, -sub_casted, "min neg subnormal"); + } + }}; +} + /// Many of these test values are taken from /// https://github.com/WebAssembly/testsuite/blob/master/conversions.wast. fn casts() { - // f32 -> i8 - test_both_cast::(127.99, 127); - test_both_cast::(-128.99, -128); - - // f32 -> i32 - test_both_cast::(0.0, 0); - test_both_cast::(-0.0, 0); - test_both_cast::(/*0x1p-149*/ f32::from_bits(0x00000001), 0); - test_both_cast::(/*-0x1p-149*/ f32::from_bits(0x80000001), 0); - test_both_cast::(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1); - test_both_cast::(/*-0x1.19999ap+0*/ f32::from_bits(0xbf8ccccd), -1); - test_both_cast::(1.9, 1); - test_both_cast::(-1.9, -1); - test_both_cast::(5.0, 5); - test_both_cast::(-5.0, -5); - test_both_cast::(2147483520.0, 2147483520); - test_both_cast::(-2147483648.0, -2147483648); - // unrepresentable casts - assert_eq::(2147483648.0f32 as i32, i32::MAX); - assert_eq::(-2147483904.0f32 as i32, i32::MIN); - assert_eq::(f32::MAX as i32, i32::MAX); - assert_eq::(f32::MIN as i32, i32::MIN); - assert_eq::(f32::INFINITY as i32, i32::MAX); - assert_eq::(f32::NEG_INFINITY as i32, i32::MIN); - assert_eq::(f32::NAN as i32, 0); - assert_eq::((-f32::NAN) as i32, 0); - - // f32 -> u32 - test_both_cast::(0.0, 0); - test_both_cast::(-0.0, 0); - test_both_cast::(-0.9999999, 0); - test_both_cast::(/*0x1p-149*/ f32::from_bits(0x1), 0); - test_both_cast::(/*-0x1p-149*/ f32::from_bits(0x80000001), 0); - test_both_cast::(/*0x1.19999ap+0*/ f32::from_bits(0x3f8ccccd), 1); - test_both_cast::(1.9, 1); - test_both_cast::(5.0, 5); - test_both_cast::(2147483648.0, 0x8000_0000); - test_both_cast::(4294967040.0, 0u32.wrapping_sub(256)); - test_both_cast::(/*-0x1.ccccccp-1*/ f32::from_bits(0xbf666666), 0); - test_both_cast::(/*-0x1.fffffep-1*/ f32::from_bits(0xbf7fffff), 0); - test_both_cast::((u32::MAX - 128) as f32, u32::MAX - 255); // rounding loss - // unrepresentable casts - assert_eq::((u32::MAX - 127) as f32 as u32, u32::MAX); // rounds up and then becomes unrepresentable - assert_eq::(4294967296.0f32 as u32, u32::MAX); - assert_eq::(-5.0f32 as u32, 0); - assert_eq::(f32::MAX as u32, u32::MAX); - assert_eq::(f32::MIN as u32, 0); - assert_eq::(f32::INFINITY as u32, u32::MAX); - assert_eq::(f32::NEG_INFINITY as u32, 0); - assert_eq::(f32::NAN as u32, 0); - assert_eq::((-f32::NAN) as u32, 0); - - // f32 -> i64 - test_both_cast::(4294967296.0, 4294967296); - test_both_cast::(-4294967296.0, -4294967296); - test_both_cast::(9223371487098961920.0, 9223371487098961920); - test_both_cast::(-9223372036854775808.0, -9223372036854775808); - - // f64 -> i8 - test_both_cast::(127.99, 127); - test_both_cast::(-128.99, -128); - - // f64 -> i32 - test_both_cast::(0.0, 0); - test_both_cast::(-0.0, 0); - test_both_cast::(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1); - test_both_cast::( - /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a), - -1, - ); - test_both_cast::(1.9, 1); - test_both_cast::(-1.9, -1); - test_both_cast::(1e8, 100_000_000); - test_both_cast::(2147483647.0, 2147483647); - test_both_cast::(-2147483648.0, -2147483648); - // unrepresentable casts - assert_eq::(2147483648.0f64 as i32, i32::MAX); - assert_eq::(-2147483649.0f64 as i32, i32::MIN); - - // f64 -> i64 - test_both_cast::(0.0, 0); - test_both_cast::(-0.0, 0); - test_both_cast::(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1), 0); - test_both_cast::( - /*-0x0.0000000000001p-1022*/ f64::from_bits(0x8000000000000001), - 0, - ); - test_both_cast::(/*0x1.199999999999ap+0*/ f64::from_bits(0x3ff199999999999a), 1); - test_both_cast::( - /*-0x1.199999999999ap+0*/ f64::from_bits(0xbff199999999999a), - -1, - ); - test_both_cast::(5.0, 5); - test_both_cast::(5.9, 5); - test_both_cast::(-5.0, -5); - test_both_cast::(-5.9, -5); - test_both_cast::(4294967296.0, 4294967296); - test_both_cast::(-4294967296.0, -4294967296); - test_both_cast::(9223372036854774784.0, 9223372036854774784); - test_both_cast::(-9223372036854775808.0, -9223372036854775808); - // unrepresentable casts - assert_eq::(9223372036854775808.0f64 as i64, i64::MAX); - assert_eq::(-9223372036854777856.0f64 as i64, i64::MIN); - assert_eq::(f64::MAX as i64, i64::MAX); - assert_eq::(f64::MIN as i64, i64::MIN); - assert_eq::(f64::INFINITY as i64, i64::MAX); - assert_eq::(f64::NEG_INFINITY as i64, i64::MIN); - assert_eq::(f64::NAN as i64, 0); - assert_eq::((-f64::NAN) as i64, 0); - - // f64 -> u64 - test_both_cast::(0.0, 0); - test_both_cast::(-0.0, 0); - test_both_cast::(-0.99999999999, 0); - test_both_cast::(5.0, 5); - test_both_cast::(1e16, 10000000000000000); - test_both_cast::((u64::MAX - 1024) as f64, u64::MAX - 2047); // rounding loss - test_both_cast::(9223372036854775808.0, 9223372036854775808); - // unrepresentable casts - assert_eq::(-5.0f64 as u64, 0); - assert_eq::((u64::MAX - 1023) as f64 as u64, u64::MAX); // rounds up and then becomes unrepresentable - assert_eq::(18446744073709551616.0f64 as u64, u64::MAX); - assert_eq::(f64::MAX as u64, u64::MAX); - assert_eq::(f64::MIN as u64, 0); - assert_eq::(f64::INFINITY as u64, u64::MAX); - assert_eq::(f64::NEG_INFINITY as u64, 0); - assert_eq::(f64::NAN as u64, 0); - assert_eq::((-f64::NAN) as u64, 0); - - // f64 -> i128 - assert_eq::(f64::MAX as i128, i128::MAX); - assert_eq::(f64::MIN as i128, i128::MIN); - - // f64 -> u128 - assert_eq::(f64::MAX as u128, u128::MAX); - assert_eq::(f64::MIN as u128, 0); + /* int <-> float generic tests */ + + test_ftoi_itof! { f: f32, i: i8, imin_f: -128.0, imax_f: 127.0 }; + test_ftoi_itof! { f: f32, i: u8, imin_f: 0.0, imax_f: 255.0 }; + test_ftoi_itof! { f: f32, i: i16, imin_f: -32_768.0, imax_f: 32_767.0 }; + test_ftoi_itof! { f: f32, i: u16, imin_f: 0.0, imax_f: 65_535.0 }; + test_ftoi_itof! { f: f32, i: i32, imin_f: -2_147_483_648.0, imax_f: 2_147_483_647.0 }; + test_ftoi_itof! { f: f32, i: u32, imin_f: 0.0, imax_f: 4_294_967_295.0 }; + test_ftoi_itof! { + f: f32, + i: i64, + imin_f: -9_223_372_036_854_775_808.0, + imax_f: 9_223_372_036_854_775_807.0 + }; + test_ftoi_itof! { f: f32, i: u64, imin_f: 0.0, imax_f: 18_446_744_073_709_551_615.0 }; + test_ftoi_itof! { + f: f32, + i: i128, + imin_f: -170_141_183_460_469_231_731_687_303_715_884_105_728.0, + imax_f: 170_141_183_460_469_231_731_687_303_715_884_105_727.0, + }; + test_ftoi_itof! { + f: f32, + i: u128, + imin_f: 0.0, + imax_f: 340_282_366_920_938_463_463_374_607_431_768_211_455.0 + }; + + test_ftoi_itof! { f: f64, i: i8, imin_f: -128.0, imax_f: 127.0 }; + test_ftoi_itof! { f: f64, i: u8, imin_f: 0.0, imax_f: 255.0 }; + test_ftoi_itof! { f: f64, i: i16, imin_f: -32_768.0, imax_f: 32_767.0 }; + test_ftoi_itof! { f: f64, i: u16, imin_f: 0.0, imax_f: 65_535.0 }; + test_ftoi_itof! { f: f64, i: i32, imin_f: -2_147_483_648.0, imax_f: 2_147_483_647.0 }; + test_ftoi_itof! { f: f64, i: u32, imin_f: 0.0, imax_f: 4_294_967_295.0 }; + test_ftoi_itof! { + f: f64, + i: i64, + imin_f: -9_223_372_036_854_775_808.0, + imax_f: 9_223_372_036_854_775_807.0 + }; + test_ftoi_itof! { f: f64, i: u64, imin_f: 0.0, imax_f: 18_446_744_073_709_551_615.0 }; + test_ftoi_itof! { + f: f64, + i: i128, + imin_f: -170_141_183_460_469_231_731_687_303_715_884_105_728.0, + imax_f: 170_141_183_460_469_231_731_687_303_715_884_105_727.0, + }; + test_ftoi_itof! { + f: f64, + i: u128, + imin_f: 0.0, + imax_f: 340_282_366_920_938_463_463_374_607_431_768_211_455.0 + }; + + /* int <-> float spot checks */ // int -> f32 - assert_eq::(127i8 as f32, 127.0); - assert_eq::(2147483647i32 as f32, 2147483648.0); - assert_eq::((-2147483648i32) as f32, -2147483648.0); assert_eq::(1234567890i32 as f32, /*0x1.26580cp+30*/ f32::from_bits(0x4e932c06)); - assert_eq::(16777217i32 as f32, 16777216.0); - assert_eq::((-16777217i32) as f32, -16777216.0); - assert_eq::(16777219i32 as f32, 16777220.0); - assert_eq::((-16777219i32) as f32, -16777220.0); assert_eq::( 0x7fffff4000000001i64 as f32, /*0x1.fffffep+62*/ f32::from_bits(0x5effffff), @@ -313,36 +539,23 @@ fn casts() { 0xffdfffffdfffffffu64 as i64 as f32, /*-0x1.000002p+53*/ f32::from_bits(0xda000001), ); - assert_eq::(i128::MIN as f32, -170141183460469231731687303715884105728.0f32); - assert_eq::(u128::MAX as f32, f32::INFINITY); // saturation // int -> f64 - assert_eq::(127i8 as f64, 127.0); - assert_eq::(i16::MIN as f64, -32768.0f64); - assert_eq::(2147483647i32 as f64, 2147483647.0); - assert_eq::(-2147483648i32 as f64, -2147483648.0); assert_eq::(987654321i32 as f64, 987654321.0); - assert_eq::(9223372036854775807i64 as f64, 9223372036854775807.0); - assert_eq::(-9223372036854775808i64 as f64, -9223372036854775808.0); assert_eq::(4669201609102990i64 as f64, 4669201609102990.0); // Feigenbaum (?) assert_eq::(9007199254740993i64 as f64, 9007199254740992.0); assert_eq::(-9007199254740993i64 as f64, -9007199254740992.0); assert_eq::(9007199254740995i64 as f64, 9007199254740996.0); assert_eq::(-9007199254740995i64 as f64, -9007199254740996.0); - assert_eq::(u128::MAX as f64, 340282366920938463463374607431768211455.0f64); // even that fits... + + /* float -> float generic tests */ + + test_ftof! { f1: f32, f2: f64 }; + test_ftof! { f1: f64, f2: f32 }; + + /* float -> float spot checks */ // f32 -> f64 - assert_eq::((0.0f32 as f64).to_bits(), 0.0f64.to_bits()); - assert_eq::(((-0.0f32) as f64).to_bits(), (-0.0f64).to_bits()); - assert_eq::(5.0f32 as f64, 5.0f64); - assert_eq::( - /*0x1p-149*/ f32::from_bits(0x1) as f64, - /*0x1p-149*/ f64::from_bits(0x36a0000000000000), - ); - assert_eq::( - /*-0x1p-149*/ f32::from_bits(0x80000001) as f64, - /*-0x1p-149*/ f64::from_bits(0xb6a0000000000000), - ); assert_eq::( /*0x1.fffffep+127*/ f32::from_bits(0x7f7fffff) as f64, /*0x1.fffffep+127*/ f64::from_bits(0x47efffffe0000000), @@ -359,15 +572,8 @@ fn casts() { /*0x1.8f867ep+125*/ f32::from_bits(0x7e47c33f) as f64, 6.6382536710104395e+37, ); - assert_eq::(f32::INFINITY as f64, f64::INFINITY); - assert_eq::(f32::NEG_INFINITY as f64, f64::NEG_INFINITY); // f64 -> f32 - assert_eq::((0.0f64 as f32).to_bits(), 0.0f32.to_bits()); - assert_eq::(((-0.0f64) as f32).to_bits(), (-0.0f32).to_bits()); - assert_eq::(5.0f64 as f32, 5.0f32); - assert_eq::(/*0x0.0000000000001p-1022*/ f64::from_bits(0x1) as f32, 0.0); - assert_eq::(/*-0x0.0000000000001p-1022*/ (-f64::from_bits(0x1)) as f32, -0.0); assert_eq::( /*0x1.fffffe0000000p-127*/ f64::from_bits(0x380fffffe0000000) as f32, /*0x1p-149*/ f32::from_bits(0x800000), @@ -376,10 +582,6 @@ fn casts() { /*0x1.4eae4f7024c7p+108*/ f64::from_bits(0x46b4eae4f7024c70) as f32, /*0x1.4eae5p+108*/ f32::from_bits(0x75a75728), ); - assert_eq::(f64::MAX as f32, f32::INFINITY); - assert_eq::(f64::MIN as f32, f32::NEG_INFINITY); - assert_eq::(f64::INFINITY as f32, f32::INFINITY); - assert_eq::(f64::NEG_INFINITY as f32, f32::NEG_INFINITY); } fn ops() {