From 617721363674b780dc8b9f507e99a990441934ab 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. --- tests/pass/float.rs | 553 +++++++++++++++++++++++++++++--------------- 1 file changed, 368 insertions(+), 185 deletions(-) diff --git a/tests/pass/float.rs b/tests/pass/float.rs index 5464627fa1..406d1f79db 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::convert::TryFrom; +use std::fmt::{Debug, LowerHex}; use std::hint::black_box; use std::{f32, f64}; @@ -29,15 +32,78 @@ 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 { + type Int: Int; + const BITS: u32; + const EXPONENT_BITS: u32; + const SIGNIFICAND_BITS: u32 = Self::BITS - Self::EXPONENT_BITS - 1; + /// The maximum value of the exponent + const EXPONENT_MAX: u32 = (1 << Self::EXPONENT_BITS) - 1; + /// The exponent bias value + const EXPONENT_BIAS: u32 = Self::EXPONENT_MAX >> 1; + + const ZERO: Self; + const FIVE: Self; + + fn to_bits(self) -> Self::Int; +} + +macro_rules! impl_float { + ($ty:ty, $ity:ty, $bits:literal, $sig_bits:literal) => { + impl Float for $ty { + type Int = $ity; + const BITS: u32 = $bits; + const EXPONENT_BITS: u32 = $sig_bits; + const ZERO: Self = 0.0; + const FIVE: Self = 5.0; + + fn to_bits(self) -> Self::Int { + self.to_bits() + } + } + }; } +trait Int: Copy + PartialEq + Debug + LowerHex { + /// Unsigned integer of the same size, or self if already unsigned + type Unsigned; + const SIGNED: bool; + const BITS: u32; + const ZERO: Self; + /// Negative one if signed, zero otherwise + const NEG_ONE: Self; + /// Negative five if signed, zero otherwise + const NEG_FIVE: Self; +} + +macro_rules! impl_int { + ($($sty:ty, $uty:ty);+) => { + $( + impl Int for $sty { + type Unsigned = $uty; + const SIGNED: bool = true; + const BITS: u32 = Self::BITS; + const ZERO: Self = 0; + const NEG_ONE: Self = -1; + const NEG_FIVE: Self = -5; + } + + impl Int for $uty { + type Unsigned = Self; + const SIGNED: bool = false; + const BITS: u32 = Self::BITS; + const NEG_ONE: Self = 0; + const NEG_FIVE: Self = 0; + const ZERO: Self = 0; + } + )+ + }; +} + +impl_float!(f32, u32, 32, 8); +impl_float!(f64, u64, 64, 11); +impl_int!(i8, u8; i16, u16; i32, u32; i64, u64; i128, u128); + trait FloatToInt: Copy { fn cast(self) -> Int; unsafe fn cast_unchecked(self) -> Int; @@ -64,13 +130,59 @@ 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: &str) where F: FloatToInt, I: PartialEq + Debug, { - assert_eq!(x.cast(), y); - assert_eq!(unsafe { x.cast_unchecked() }, y); + assert_eq!(x.cast(), y, "{} -> {}: {msg}", type_name::(), type_name::()); + assert_eq!( + unsafe { x.cast_unchecked() }, + y, + "{} -> {}: {msg}", + stringify!($fty), + stringify!($ity) + ); +} + +/// 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: &str) { + assert_eq!(x, y, "{msg}"); +} + +/// Check that floats have bitwise equality +fn assert_biteq(a: F, b: F, msg: &str) { + let ab = a.to_bits(); + let bb = b.to_bits(); + let msg = format!( + "({ab:#0width$x} != {bb:#0width$x}) {}: {msg}", + type_name::(), + width = (2 + F::Int::BITS / 4) as usize + ); + assert_eq_msg::(ab, bb, &msg); +} + +/// Check that two floats have equality +fn assert_feq(a: F, b: F, msg: &str) { + let ab = a.to_bits(); + let bb = b.to_bits(); + let msg = format!( + "({ab:#0width$x} != {bb:#0width$x}) {}: {msg}", + type_name::(), + width = (2 + F::Int::BITS / 4) as usize + ); + assert_eq_msg::(a, b, &msg); } fn basic() { @@ -148,155 +260,250 @@ 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) { + 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) { + let msg = format!("{} -> {}: {msg}", stringify!($fty), stringify!($ity)); + assert_eq_msg::<$ity>(f as $ity, i, &msg); + } + + /// Int to float checks + fn assert_itof(i: $ity, f: $fty, msg: &str) { + let itof_msg = format!("{} -> {}: {msg}", stringify!($ity), stringify!($fty)); + assert_eq_msg::<$fty>(i as $fty, f, &itof_msg); + } + + /// 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 + 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 ibits = <$ity>::BITS; + let imax = <$ity>::MAX; + let imin = <$ity>::MIN; + let isigned = <$ity>::SIGNED; + + #[allow(overflowing_literals)] + let imin_f = $imin_f; + #[allow(overflowing_literals)] + let imax_f = $imax_f; + + // Skip unchecked cast if negative numbers are unrepresentable + let assert_ftoi_neg = if isigned { assert_ftoi } else { assert_ftoi_unrep }; + // Skip unchecked cast when int min/max would be unrepresentable + let assert_ftoi_big = if ibits < fbits { assert_ftoi } else { assert_ftoi_unrep }; + let assert_bidir_big = if ibits < fbits { assert_bidir } else { assert_bidir_unrep }; + + // Near zero representations + assert_bidir(0.0, 0, "zero"); + assert_ftoi(-0.0, 0, "negative zero"); + assert_ftoi_neg(-0.99999999999999999999999999, <$ity>::NEG_ONE, "near -1"); + assert_ftoi(<$fty>::from_bits(0x1), 0, "min subnormal"); + assert_ftoi(<$fty>::from_bits(0x1 | 1 << (fbits - 1)), 0, "min neg subnormal"); + + // Spot checks + assert_ftoi(0.9, 0, "0.9"); + assert_ftoi_neg(-0.9, 0, "-0.9"); + assert_ftoi(1.1, 1, "1.1"); + assert_ftoi_neg(-1.1, <$ity>::NEG_ONE, "-1.1"); + assert_ftoi(1.9, 1, "1.9"); + assert_ftoi_neg(-1.9, <$ity>::NEG_ONE, "-1.9"); + assert_ftoi(5.0, 5, "5.0"); + assert_ftoi_neg(-5.0, <$ity>::NEG_FIVE, "-5.0"); + assert_ftoi(5.9, 5, "5.0"); + assert_ftoi_neg(-5.9, <$ity>::NEG_FIVE, "-5.0"); + + // Half representations should always fit perfectly + let half_i_max = + <$ity>::try_from((!(0 as <$ity as Int>::Unsigned) >> (ibits / 2)) + 1).unwrap(); + let half_i_min = <$ity>::ZERO.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"); + assert_ftoi_big(imax_f + 0.99, <$ity>::MAX, "slightly above i max"); + assert_ftoi_neg(imin_f - 0.99, <$ity>::MIN, "slightly below i min"); + + if fbits == ibits { + // Signed integers have one fewer bit of range + let adj = if isigned { 1 } else { 0 }; + + let exp_overflow = 1 << (<$fty>::EXPONENT_BITS - 1 - adj); + let exp_max = (1 << <$fty>::EXPONENT_BITS - adj) - 1; + + let max_le_imax = imax - <$ity>::try_from(exp_overflow).unwrap(); + + // Max value that can be represented below I::MAX + assert_bidir( + max_le_imax as $fty, + imax - (exp_max as $ity), + "max representable near int max", + ); + + // The next value up is unrepresentable + assert_bidir_unrep((max_le_imax + 1) as $fty, imax, "one more than max representable"); + + if isigned { + let min_ge_imin = imin + (exp_overflow as $ity); + assert_bidir(min_ge_imin as $fty, imin, "min representable near int min"); + } + } + + if fbits >= ibits { + // Max representable power of 10 + let pow10_max = (10 as $ity).pow(imax.ilog10()); + assert_bidir(pow10_max as $fty, pow10_max, "pow10 max"); + + // Float limits + assert_ftoi_unrep(<$fty>::MAX, imax, "f max"); + assert_ftoi_unrep(<$fty>::MIN, imin, "f min"); + } + + if ibits > fbits * 2 && !isigned { + // Unsigned max is enough to saturate only when more than 2x bit width + assert_itof(imax, <$fty>::INFINITY, "imax, ibits > fbits * 2"); + } else { + // Signed values and anything smaller still fit + assert_itof(imax, imax_f, "imax, ibits > fbits * 2"); + assert_itof(imin, imin_f, "imin, ibits > fbits * 2"); + } + + // 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>::ZERO; + let f2zero = <$f2>::ZERO; + + assert_biteq((f1zero as $f2), f2zero, "0.0"); + assert_biteq(((-f1zero) as $f2), (-f2zero), "-0.0"); + assert_biteq((<$f1>::FIVE as $f2), <$f2>::FIVE, "5.0"); + assert_biteq(((-<$f1>::FIVE) as $f2), (-<$f2>::FIVE), "-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 +520,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 +553,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 +563,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() {