From eac01b9712a0a50648b4bf5728ed1f4d8ae27537 Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 10 May 2024 18:25:31 +0800 Subject: [PATCH 1/3] add f16 --- build.rs | 4 + src/bounds.rs | 6 +- src/cast.rs | 87 ++++++++++++++++----- src/float.rs | 121 ++++++++++++++++++++++++++++- src/identities.rs | 4 + src/lib.rs | 186 ++++++++++++++++++++++++++++++++++++++++++--- src/ops/bytes.rs | 5 ++ src/ops/euclid.rs | 30 ++++++++ src/ops/inv.rs | 16 ++++ src/ops/mul_add.rs | 23 ++++++ src/pow.rs | 51 +++++++++++++ src/sign.rs | 10 ++- 12 files changed, 507 insertions(+), 36 deletions(-) diff --git a/build.rs b/build.rs index 98b06bef..a32ec3eb 100644 --- a/build.rs +++ b/build.rs @@ -4,4 +4,8 @@ fn main() { ac.emit_expression_cfg("1f64.total_cmp(&2f64)", "has_total_cmp"); // 1.62 autocfg::rerun_path("build.rs"); + + // FIXME: use autocfg to emit it + println!("cargo:rustc-cfg=has_f16"); + println!("cargo:rustc-check-cfg=cfg(has_f16)"); } diff --git a/src/bounds.rs b/src/bounds.rs index acc990ea..e283cb99 100644 --- a/src/bounds.rs +++ b/src/bounds.rs @@ -1,3 +1,5 @@ +#[cfg(has_f16)] +use core::f16; use core::num::Wrapping; use core::{f32, f64}; use core::{i128, i16, i32, i64, i8, isize}; @@ -77,7 +79,10 @@ impl Bounded for Wrapping { } } +#[cfg(has_f16)] +bounded_impl!(f16, f16::MIN, f16::MAX); bounded_impl!(f32, f32::MIN, f32::MAX); +bounded_impl!(f64, f64::MIN, f64::MAX); macro_rules! for_each_tuple_ { ( $m:ident !! ) => ( @@ -110,7 +115,6 @@ macro_rules! bounded_tuple { } for_each_tuple!(bounded_tuple); -bounded_impl!(f64, f64::MIN, f64::MAX); #[test] fn wrapping_bounded() { diff --git a/src/cast.rs b/src/cast.rs index a3e4dd8b..cd45316e 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -1,3 +1,5 @@ +#[cfg(has_f16)] +use core::f16; use core::mem::size_of; use core::num::Wrapping; use core::{f32, f64}; @@ -101,6 +103,15 @@ pub trait ToPrimitive { self.to_u64().map(From::from) } + /// Converts the value of `self` to an `f16`. Overflows may map to positive + /// or negative inifinity, otherwise `None` is returned if the value cannot + /// be represented by an `f16`. + #[cfg(has_f16)] + #[inline] + fn to_f16(&self) -> Option { + self.to_f64().as_ref().and_then(ToPrimitive::to_f16) + } + /// Converts the value of `self` to an `f32`. Overflows may map to positive /// or negative inifinity, otherwise `None` is returned if the value cannot /// be represented by an `f32`. @@ -177,6 +188,11 @@ macro_rules! impl_to_primitive_int { fn to_u128 -> u128; } + #[cfg(has_f16)] + #[inline] + fn to_f16(&self) -> Option { + Some(*self as f16) + } #[inline] fn to_f32(&self) -> Option { Some(*self as f32) @@ -247,6 +263,11 @@ macro_rules! impl_to_primitive_uint { fn to_u128 -> u128; } + #[cfg(has_f16)] + #[inline] + fn to_f16(&self) -> Option { + Some(*self as f16) + } #[inline] fn to_f32(&self) -> Option { Some(*self as f32) @@ -267,8 +288,9 @@ impl_to_primitive_uint!(u64); impl_to_primitive_uint!(u128); macro_rules! impl_to_primitive_float_to_float { - ($SrcT:ident : $( fn $method:ident -> $DstT:ident ; )*) => {$( + ($SrcT:ident : $( $(#[$cfg:meta])* fn $method:ident -> $DstT:ident ; )*) => {$( #[inline] + $(#[$cfg])* fn $method(&self) -> Option<$DstT> { // We can safely cast all values, whether NaN, +-inf, or finite. // Finite values that are reducing size may saturate to +-inf. @@ -364,6 +386,8 @@ macro_rules! impl_to_primitive_float { } impl_to_primitive_float_to_float! { $T: + #[cfg(has_f16)] + fn to_f16 -> f16; fn to_f32 -> f32; fn to_f64 -> f64; } @@ -371,6 +395,8 @@ macro_rules! impl_to_primitive_float { }; } +#[cfg(has_f16)] +impl_to_primitive_float!(f16); impl_to_primitive_float!(f32); impl_to_primitive_float!(f64); @@ -469,6 +495,14 @@ pub trait FromPrimitive: Sized { n.to_u64().and_then(FromPrimitive::from_u64) } + /// Converts a `f16` to return an optional value of this type. If the + /// value cannot be represented by this type, then `None` is returned. + #[cfg(has_f16)] + #[inline] + fn from_f16(n: f16) -> Option { + FromPrimitive::from_f64(n as f64) + } + /// Converts a `f32` to return an optional value of this type. If the /// value cannot be represented by this type, then `None` is returned. #[inline] @@ -545,6 +579,11 @@ macro_rules! impl_from_primitive { n.$to_ty() } + #[cfg(has_f16)] + #[inline] + fn from_f16(n: f16) -> Option<$T> { + n.$to_ty() + } #[inline] fn from_f32(n: f32) -> Option<$T> { n.$to_ty() @@ -569,6 +608,8 @@ impl_from_primitive!(u16, to_u16); impl_from_primitive!(u32, to_u32); impl_from_primitive!(u64, to_u64); impl_from_primitive!(u128, to_u128); +#[cfg(has_f16)] +impl_from_primitive!(f16, to_f16); impl_from_primitive!(f32, to_f32); impl_from_primitive!(f64, to_f64); @@ -598,6 +639,8 @@ impl ToPrimitive for Wrapping { fn to_u64 -> u64; fn to_u128 -> u128; + #[cfg(has_f16)] + fn to_f16 -> f16; fn to_f32 -> f32; fn to_f64 -> f64; } @@ -629,6 +672,8 @@ impl FromPrimitive for Wrapping { fn from_u64(u64); fn from_u128(u128); + #[cfg(has_f16)] + fn from_f16(f16); fn from_f32(f32); fn from_f64(f64); } @@ -692,6 +737,8 @@ impl_num_cast!(i32, to_i32); impl_num_cast!(i64, to_i64); impl_num_cast!(i128, to_i128); impl_num_cast!(isize, to_isize); +#[cfg(has_f16)] +impl_num_cast!(f16, to_f16); impl_num_cast!(f32, to_f32); impl_num_cast!(f64, to_f64); @@ -742,29 +789,31 @@ macro_rules! impl_as_primitive { #[inline] fn as_(self) -> $U { self as $U } } }; - (@ $T: ty => { $( $U: ty ),* } ) => {$( - impl_as_primitive!(@ $T => impl $U); + (@ $T: ty => { $( $(#[$cfg:meta])* $U: ty ),* } ) => {$( + impl_as_primitive!(@ $T => $(#[$cfg])* impl $U); )*}; - ($T: ty => { $( $U: ty ),* } ) => { - impl_as_primitive!(@ $T => { $( $U ),* }); + ($T: ty => { $( $(#[$cfg:meta])* $U: ty ),* } ) => { + impl_as_primitive!(@ $T => { $( $(#[$cfg])* $U ),* }); impl_as_primitive!(@ $T => { u8, u16, u32, u64, u128, usize }); impl_as_primitive!(@ $T => { i8, i16, i32, i64, i128, isize }); }; } -impl_as_primitive!(u8 => { char, f32, f64 }); -impl_as_primitive!(i8 => { f32, f64 }); -impl_as_primitive!(u16 => { f32, f64 }); -impl_as_primitive!(i16 => { f32, f64 }); -impl_as_primitive!(u32 => { f32, f64 }); -impl_as_primitive!(i32 => { f32, f64 }); -impl_as_primitive!(u64 => { f32, f64 }); -impl_as_primitive!(i64 => { f32, f64 }); -impl_as_primitive!(u128 => { f32, f64 }); -impl_as_primitive!(i128 => { f32, f64 }); -impl_as_primitive!(usize => { f32, f64 }); -impl_as_primitive!(isize => { f32, f64 }); -impl_as_primitive!(f32 => { f32, f64 }); -impl_as_primitive!(f64 => { f32, f64 }); +impl_as_primitive!(u8 => { char, #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(i8 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(u16 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(i16 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(u32 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(i32 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(u64 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(i64 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(u128 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(i128 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(usize => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(isize => { #[cfg(has_f16)] f16, f32, f64 }); +#[cfg(has_f16)] +impl_as_primitive!(f16 => { f16, f32, f64 }); +impl_as_primitive!(f32 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(f64 => { #[cfg(has_f16)] f16, f32, f64 }); impl_as_primitive!(char => { char }); impl_as_primitive!(bool => {}); diff --git a/src/float.rs b/src/float.rs index 4124e92c..2bca5d26 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2,6 +2,8 @@ use core::cmp::Ordering; use core::num::FpCategory; use core::ops::{Add, Div, Neg}; +#[cfg(has_f16)] +use core::f16; use core::f32; use core::f64; @@ -798,6 +800,56 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { fn integer_decode(self) -> (u64, i16, i8); } +#[cfg(has_f16)] +impl FloatCore for f16 { + constant! { + infinity() -> f16::INFINITY; + neg_infinity() -> f16::NEG_INFINITY; + nan() -> f16::NAN; + neg_zero() -> -0.0; + min_value() -> f16::MIN; + min_positive_value() -> f16::MIN_POSITIVE; + epsilon() -> f16::EPSILON; + max_value() -> f16::MAX; + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + integer_decode_f16(self) + } + + forward! { + Self::is_nan(self) -> bool; + Self::is_infinite(self) -> bool; + Self::is_finite(self) -> bool; + Self::is_normal(self) -> bool; + Self::is_subnormal(self) -> bool; + Self::classify(self) -> FpCategory; + Self::is_sign_positive(self) -> bool; + Self::is_sign_negative(self) -> bool; + Self::min(self, other: Self) -> Self; + Self::max(self, other: Self) -> Self; + Self::recip(self) -> Self; + Self::to_degrees(self) -> Self; + Self::to_radians(self) -> Self; + } + + #[cfg(feature = "std")] + forward! { + Self::floor(self) -> Self; + Self::ceil(self) -> Self; + Self::round(self) -> Self; + Self::trunc(self) -> Self; + Self::fract(self) -> Self; + Self::abs(self) -> Self; + Self::signum(self) -> Self; + Self::powi(self, n: i32) -> Self; + } + + // TODO: + // use floor, ceil, round, trunc, abs and fract provided by libm +} + impl FloatCore for f32 { constant! { infinity() -> f32::INFINITY; @@ -1927,9 +1979,12 @@ macro_rules! float_impl_std { } #[inline] - #[allow(deprecated)] fn abs_sub(self, other: Self) -> Self { - <$T>::abs_sub(self, other) + if self <= other { + 0.0 + } else { + self - other + } } #[inline] @@ -2046,6 +2101,21 @@ macro_rules! float_impl_libm { }; } +#[cfg(has_f16)] +fn integer_decode_f16(f: f16) -> (u64, i16, i8) { + let bits: u16 = f.to_bits(); + let sign: i8 = if bits >> 15 == 0 { 1 } else { -1 }; + let mut exponent: i16 = ((bits >> 10) & 0x1f) as i16; + let mantissa = if exponent == 0 { + (bits & 0x3ff) << 1 + } else { + (bits & 0x3ff) | 0x400 + }; + // Exponent bias + mantissa shift + exponent -= 15 + 10; + (mantissa as u64, exponent, sign) +} + fn integer_decode_f32(f: f32) -> (u64, i16, i8) { let bits: u32 = f.to_bits(); let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; @@ -2074,6 +2144,9 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) { (mantissa, exponent, sign) } +#[cfg(has_f16)] +#[cfg(feature = "std")] +float_impl_std!(f16 integer_decode_f16); #[cfg(feature = "std")] float_impl_std!(f32 integer_decode_f32); #[cfg(feature = "std")] @@ -2192,6 +2265,8 @@ macro_rules! float_const_impl { Self::LN_10() / Self::LN_2() } } + #[cfg(has_f16)] + float_const_impl! { @float f16, $($constant,)+ } float_const_impl! { @float f32, $($constant,)+ } float_const_impl! { @float f64, $($constant,)+ } ); @@ -2324,6 +2399,8 @@ macro_rules! totalorder_impl { } totalorder_impl!(f64, i64, u64, 64); totalorder_impl!(f32, i32, u32, 32); +#[cfg(has_f16)] +totalorder_impl!(f16, i16, u16, 16); #[cfg(test)] mod tests { @@ -2350,6 +2427,13 @@ mod tests { let (deg, rad) = (deg as f32, rad as f32); assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-5); assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-5); + + #[cfg(has_f16)] + { + let (deg, rad) = (deg as f16, rad as f16); + assert!((FloatCore::to_degrees(rad) - deg).abs() < 1.0); + assert!((FloatCore::to_radians(deg) - rad).abs() < 0.01); + } } } @@ -2365,6 +2449,13 @@ mod tests { let (deg, rad) = (deg as f32, rad as f32); assert!((Float::to_degrees(rad) - deg).abs() < 1e-5); assert!((Float::to_radians(deg) - rad).abs() < 1e-5); + + #[cfg(has_f16)] + { + let (deg, rad) = (deg as f16, rad as f16); + assert!((Float::to_degrees(rad) - deg).abs() < 1.0); + assert!((Float::to_radians(deg) - rad).abs() < 0.01); + } } } @@ -2393,6 +2484,8 @@ mod tests { assert!((F::LOG2_10() - F::LN_10() / F::LN_2()).abs() < diff); } + #[cfg(has_f16)] + check::(1e-2); check::(1e-6); check::(1e-12); } @@ -2401,6 +2494,8 @@ mod tests { #[cfg(any(feature = "std", feature = "libm"))] fn copysign() { use crate::float::Float; + #[cfg(has_f16)] + test_copysign_generic(2.0_f16, -2.0_f16, f16::nan()); test_copysign_generic(2.0_f32, -2.0_f32, f32::nan()); test_copysign_generic(2.0_f64, -2.0_f64, f64::nan()); test_copysignf(2.0_f32, -2.0_f32, f32::nan()); @@ -2455,6 +2550,8 @@ mod tests { fn subnormal() { test_subnormal::(); test_subnormal::(); + #[cfg(has_f16)] + test_subnormal::(); } #[test] @@ -2475,9 +2572,13 @@ mod tests { check_eq(f64::NAN, f64::NAN); check_eq(f32::NAN, f32::NAN); + #[cfg(has_f16)] + check_eq(f16::NAN, f16::NAN); check_lt(-0.0_f64, 0.0_f64); check_lt(-0.0_f32, 0.0_f32); + #[cfg(has_f16)] + check_lt(-0.0_f16, 0.0_f16); // x87 registers don't preserve the exact value of signaling NaN: // https://github.com/rust-lang/rust/issues/115567 @@ -2498,6 +2599,14 @@ mod tests { let neg_s_nan = f32::from_bits(0xffa00000); let neg_q_nan = f32::from_bits(0xffc00000); check_lt(neg_q_nan, neg_s_nan); + + let s_nan = f16::from_bits(0x7a00); + let q_nan = f16::from_bits(0x7e00); + check_lt(s_nan, q_nan); + + let neg_s_nan = f16::from_bits(0xfa00); + let neg_q_nan = f16::from_bits(0xfe00); + check_lt(neg_q_nan, neg_s_nan); } check_lt(-f64::NAN, f64::NEG_INFINITY); @@ -2509,5 +2618,13 @@ mod tests { check_gt(1.0_f32, -f32::NAN); check_lt(f32::INFINITY, f32::NAN); check_gt(f32::NAN, 1.0_f32); + + #[cfg(has_f16)] + { + check_lt(-f16::NAN, f16::NEG_INFINITY); + check_gt(1.0_f16, -f16::NAN); + check_lt(f16::INFINITY, f16::NAN); + check_gt(f16::NAN, 1.0_f16); + } } } diff --git a/src/identities.rs b/src/identities.rs index c30cd1dc..0575cbe3 100644 --- a/src/identities.rs +++ b/src/identities.rs @@ -68,6 +68,8 @@ zero_impl!(i32, 0); zero_impl!(i64, 0); zero_impl!(i128, 0); +#[cfg(has_f16)] +zero_impl!(f16, 0.0); zero_impl!(f32, 0.0); zero_impl!(f64, 0.0); @@ -173,6 +175,8 @@ one_impl!(i32, 1); one_impl!(i64, 1); one_impl!(i128, 1); +#[cfg(has_f16)] +one_impl!(f16, 1.0); one_impl!(f32, 1.0); one_impl!(f64, 1.0); diff --git a/src/lib.rs b/src/lib.rs index d392e920..f350a469 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ #![doc(html_root_url = "https://docs.rs/num-traits/0.2")] #![deny(unconditional_recursion)] #![no_std] +#![cfg_attr(has_f16, feature(f16))] // FIXME: remove it when it's stablized // Need to explicitly bring the crate in for inherent float methods #[cfg(feature = "std")] @@ -219,6 +220,169 @@ fn str_to_ascii_lower_eq_str(a: &str, b: &str) -> bool { // with this implementation ourselves until we want to make a breaking change. // (would have to drop it from `Num` though) macro_rules! float_trait_impl { + // FIXME: remove it if `f128: FromStr` is implemented + (no_fast_path $name:ident for $($t:ident)*) => ($( + impl $name for $t { + type FromStrRadixErr = ParseFloatError; + + fn from_str_radix(src: &str, radix: u32) + -> Result + { + use self::FloatErrorKind::*; + use self::ParseFloatError as PFE; + + // Special values + if str_to_ascii_lower_eq_str(src, "inf") + || str_to_ascii_lower_eq_str(src, "infinity") + { + return Ok($t::INFINITY); + } else if str_to_ascii_lower_eq_str(src, "-inf") + || str_to_ascii_lower_eq_str(src, "-infinity") + { + return Ok($t::NEG_INFINITY); + } else if str_to_ascii_lower_eq_str(src, "nan") { + return Ok($t::NAN); + } else if str_to_ascii_lower_eq_str(src, "-nan") { + return Ok(-$t::NAN); + } + + fn slice_shift_char(src: &str) -> Option<(char, &str)> { + let mut chars = src.chars(); + Some((chars.next()?, chars.as_str())) + } + + let (is_positive, src) = match slice_shift_char(src) { + None => return Err(PFE { kind: Empty }), + Some(('-', "")) => return Err(PFE { kind: Empty }), + Some(('-', src)) => (false, src), + Some((_, _)) => (true, src), + }; + + // The significand to accumulate + let mut sig = if is_positive { 0.0 } else { -0.0 }; + // Necessary to detect overflow + let mut prev_sig = sig; + let mut cs = src.chars().enumerate(); + // Exponent prefix and exponent index offset + let mut exp_info = None::<(char, usize)>; + + // Parse the integer part of the significand + for (i, c) in cs.by_ref() { + match c.to_digit(radix) { + Some(digit) => { + // shift significand one digit left + sig *= radix as $t; + + // add/subtract current digit depending on sign + if is_positive { + sig += (digit as isize) as $t; + } else { + sig -= (digit as isize) as $t; + } + + // Detect overflow by comparing to last value, except + // if we've not seen any non-zero digits. + if prev_sig != 0.0 { + if is_positive && sig <= prev_sig + { return Ok($t::INFINITY); } + if !is_positive && sig >= prev_sig + { return Ok($t::NEG_INFINITY); } + + // Detect overflow by reversing the shift-and-add process + if is_positive && (prev_sig != (sig - digit as $t) / radix as $t) + { return Ok($t::INFINITY); } + if !is_positive && (prev_sig != (sig + digit as $t) / radix as $t) + { return Ok($t::NEG_INFINITY); } + } + prev_sig = sig; + }, + None => match c { + 'e' | 'E' | 'p' | 'P' => { + exp_info = Some((c, i + 1)); + break; // start of exponent + }, + '.' => { + break; // start of fractional part + }, + _ => { + return Err(PFE { kind: Invalid }); + }, + }, + } + } + + // If we are not yet at the exponent parse the fractional + // part of the significand + if exp_info.is_none() { + let mut power = 1.0; + for (i, c) in cs.by_ref() { + match c.to_digit(radix) { + Some(digit) => { + // Decrease power one order of magnitude + power /= radix as $t; + // add/subtract current digit depending on sign + sig = if is_positive { + sig + (digit as $t) * power + } else { + sig - (digit as $t) * power + }; + // Detect overflow by comparing to last value + if is_positive && sig < prev_sig + { return Ok($t::INFINITY); } + if !is_positive && sig > prev_sig + { return Ok($t::NEG_INFINITY); } + prev_sig = sig; + }, + None => match c { + 'e' | 'E' | 'p' | 'P' => { + exp_info = Some((c, i + 1)); + break; // start of exponent + }, + _ => { + return Err(PFE { kind: Invalid }); + }, + }, + } + } + } + + // Parse and calculate the exponent + let exp = match exp_info { + Some((c, offset)) => { + let base = match c { + 'E' | 'e' if radix == 10 => 10.0, + 'P' | 'p' if radix == 16 => 2.0, + _ => return Err(PFE { kind: Invalid }), + }; + + // Parse the exponent as decimal integer + let src = &src[offset..]; + let (is_positive, exp) = match slice_shift_char(src) { + Some(('-', src)) => (false, src.parse::()), + Some(('+', src)) => (true, src.parse::()), + Some((_, _)) => (true, src.parse::()), + None => return Err(PFE { kind: Invalid }), + }; + + #[cfg(feature = "std")] + fn pow(base: $t, exp: usize) -> $t { + Float::powi(base, exp as i32) + } + // otherwise uses the generic `pow` from the root + + match (is_positive, exp) { + (true, Ok(exp)) => pow(base, exp), + (false, Ok(exp)) => 1.0 / pow(base, exp), + (_, Err(_)) => return Err(PFE { kind: Invalid }), + } + }, + None => 1.0, // no exponent + }; + + Ok(sig * exp) + } + } + )*); ($name:ident for $($t:ident)*) => ($( impl $name for $t { type FromStrRadixErr = ParseFloatError; @@ -240,15 +404,15 @@ macro_rules! float_trait_impl { if str_to_ascii_lower_eq_str(src, "inf") || str_to_ascii_lower_eq_str(src, "infinity") { - return Ok(core::$t::INFINITY); + return Ok($t::INFINITY); } else if str_to_ascii_lower_eq_str(src, "-inf") || str_to_ascii_lower_eq_str(src, "-infinity") { - return Ok(core::$t::NEG_INFINITY); + return Ok($t::NEG_INFINITY); } else if str_to_ascii_lower_eq_str(src, "nan") { - return Ok(core::$t::NAN); + return Ok($t::NAN); } else if str_to_ascii_lower_eq_str(src, "-nan") { - return Ok(-core::$t::NAN); + return Ok(-$t::NAN); } fn slice_shift_char(src: &str) -> Option<(char, &str)> { @@ -289,15 +453,15 @@ macro_rules! float_trait_impl { // if we've not seen any non-zero digits. if prev_sig != 0.0 { if is_positive && sig <= prev_sig - { return Ok(core::$t::INFINITY); } + { return Ok($t::INFINITY); } if !is_positive && sig >= prev_sig - { return Ok(core::$t::NEG_INFINITY); } + { return Ok($t::NEG_INFINITY); } // Detect overflow by reversing the shift-and-add process if is_positive && (prev_sig != (sig - digit as $t) / radix as $t) - { return Ok(core::$t::INFINITY); } + { return Ok($t::INFINITY); } if !is_positive && (prev_sig != (sig + digit as $t) / radix as $t) - { return Ok(core::$t::NEG_INFINITY); } + { return Ok($t::NEG_INFINITY); } } prev_sig = sig; }, @@ -333,9 +497,9 @@ macro_rules! float_trait_impl { }; // Detect overflow by comparing to last value if is_positive && sig < prev_sig - { return Ok(core::$t::INFINITY); } + { return Ok($t::INFINITY); } if !is_positive && sig > prev_sig - { return Ok(core::$t::NEG_INFINITY); } + { return Ok($t::NEG_INFINITY); } prev_sig = sig; }, None => match c { @@ -389,6 +553,8 @@ macro_rules! float_trait_impl { } )*) } +#[cfg(has_f16)] +float_trait_impl!(Num for f16); float_trait_impl!(Num for f32 f64); /// A value bounded by a minimum and a maximum diff --git a/src/ops/bytes.rs b/src/ops/bytes.rs index f6a8030a..7b9cab15 100644 --- a/src/ops/bytes.rs +++ b/src/ops/bytes.rs @@ -252,6 +252,8 @@ int_to_from_bytes_impl!(isize, 8); #[cfg(target_pointer_width = "32")] int_to_from_bytes_impl!(isize, 4); +#[cfg(has_f16)] +float_to_from_bytes_impl!(f16, 2); float_to_from_bytes_impl!(f32, 4); float_to_from_bytes_impl!(f64, 8); @@ -312,6 +314,9 @@ mod tests { )+} } + #[cfg(has_f16)] + check_to_from_bytes!(f16); + check_to_from_bytes!(f32 f64); } } diff --git a/src/ops/euclid.rs b/src/ops/euclid.rs index 194427d2..eaf32308 100644 --- a/src/ops/euclid.rs +++ b/src/ops/euclid.rs @@ -88,9 +88,36 @@ macro_rules! euclid_forward_impl { euclid_forward_impl!(isize i8 i16 i32 i64 i128); euclid_forward_impl!(usize u8 u16 u32 u64 u128); +#[cfg(has_f16)] +#[cfg(feature = "std")] +euclid_forward_impl!(f16); + #[cfg(feature = "std")] euclid_forward_impl!(f32 f64); +#[cfg(has_f16)] +#[cfg(not(feature = "std"))] +impl Euclid for f16 { + #[inline] + fn div_euclid(&self, v: &f16) -> f16 { + let q = ::trunc(self / v); + if self % v < 0.0 { + return if *v > 0.0 { q - 1.0 } else { q + 1.0 }; + } + q + } + + #[inline] + fn rem_euclid(&self, v: &f16) -> f16 { + let r = self % v; + if r < 0.0 { + r + ::abs(*v) + } else { + r + } + } +} + #[cfg(not(feature = "std"))] impl Euclid for f32 { #[inline] @@ -256,6 +283,9 @@ mod tests { }; } + #[cfg(has_f16)] + test_euclid!(f16); + test_euclid!(f32 f64); } diff --git a/src/ops/inv.rs b/src/ops/inv.rs index 7087d09d..17076e5e 100644 --- a/src/ops/inv.rs +++ b/src/ops/inv.rs @@ -17,6 +17,14 @@ pub trait Inv { fn inv(self) -> Self::Output; } +#[cfg(has_f16)] +impl Inv for f16 { + type Output = f16; + #[inline] + fn inv(self) -> f16 { + 1.0 / self + } +} impl Inv for f32 { type Output = f32; #[inline] @@ -31,6 +39,14 @@ impl Inv for f64 { 1.0 / self } } +#[cfg(has_f16)] +impl<'a> Inv for &'a f16 { + type Output = f16; + #[inline] + fn inv(self) -> f16 { + 1.0 / *self + } +} impl<'a> Inv for &'a f32 { type Output = f32; #[inline] diff --git a/src/ops/mul_add.rs b/src/ops/mul_add.rs index 51beb557..b60656cd 100644 --- a/src/ops/mul_add.rs +++ b/src/ops/mul_add.rs @@ -34,6 +34,17 @@ pub trait MulAddAssign { fn mul_add_assign(&mut self, a: A, b: B); } +#[cfg(has_f16)] +#[cfg(any(feature = "std", feature = "libm"))] +impl MulAdd for f16 { + type Output = Self; + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + ::mul_add(self, a, b) + } +} + #[cfg(any(feature = "std", feature = "libm"))] impl MulAdd for f32 { type Output = Self; @@ -70,6 +81,15 @@ macro_rules! mul_add_impl { mul_add_impl!(MulAdd for isize i8 i16 i32 i64 i128); mul_add_impl!(MulAdd for usize u8 u16 u32 u64 u128); +#[cfg(has_f16)] +#[cfg(any(feature = "std", feature = "libm"))] +impl MulAddAssign for f16 { + #[inline] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self = ::mul_add(*self, a, b) + } +} + #[cfg(any(feature = "std", feature = "libm"))] impl MulAddAssign for f32 { #[inline] @@ -144,6 +164,9 @@ mod tests { }; } + #[cfg(has_f16)] + test_mul_add!(f16); + test_mul_add!(f32 f64); } } diff --git a/src/pow.rs b/src/pow.rs index ef51c954..fc993903 100644 --- a/src/pow.rs +++ b/src/pow.rs @@ -31,6 +31,41 @@ macro_rules! pow_impl { ($t:ty, $rhs:ty) => { pow_impl!($t, $rhs, usize, pow); }; + // `From` is not implemented for `f32`. + // See also https://github.com/rust-lang/rust/issues/123831 + ($t:ty, f16, $desired_rhs:ty, $method:expr) => { + impl Pow for $t { + type Output = $t; + #[inline] + fn pow(self, rhs: f16) -> $t { + ($method)(self, rhs as $desired_rhs) + } + } + + impl<'a> Pow<&'a f16> for $t { + type Output = $t; + #[inline] + fn pow(self, rhs: &'a f16) -> $t { + ($method)(self, *rhs as $desired_rhs) + } + } + + impl<'a> Pow for &'a $t { + type Output = $t; + #[inline] + fn pow(self, rhs: f16) -> $t { + ($method)(*self, rhs as $desired_rhs) + } + } + + impl<'a, 'b> Pow<&'a f16> for &'b $t { + type Output = $t; + #[inline] + fn pow(self, rhs: &'a f16) -> $t { + ($method)(*self, *rhs as $desired_rhs) + } + } + }; ($t:ty, $rhs:ty, $desired_rhs:ty, $method:expr) => { impl Pow<$rhs> for $t { type Output = $t; @@ -147,6 +182,16 @@ mod float_impls { use super::Pow; use crate::Float; + #[cfg(has_f16)] + pow_impl!(f16, i8, i32, ::powi); + #[cfg(has_f16)] + pow_impl!(f16, u8, i32, ::powi); + #[cfg(has_f16)] + pow_impl!(f16, i16, i32, ::powi); + #[cfg(has_f16)] + pow_impl!(f16, u16, i32, ::powi); + #[cfg(has_f16)] + pow_impl!(f16, i32, i32, ::powi); pow_impl!(f32, i8, i32, ::powi); pow_impl!(f32, u8, i32, ::powi); pow_impl!(f32, i16, i32, ::powi); @@ -157,7 +202,13 @@ mod float_impls { pow_impl!(f64, i16, i32, ::powi); pow_impl!(f64, u16, i32, ::powi); pow_impl!(f64, i32, i32, ::powi); + #[cfg(has_f16)] + pow_impl!(f16, f16, f16, ::powf); + #[cfg(has_f16)] + pow_impl!(f32, f16, f32, ::powf); pow_impl!(f32, f32, f32, ::powf); + #[cfg(has_f16)] + pow_impl!(f64, f16, f64, ::powf); pow_impl!(f64, f32, f64, ::powf); pow_impl!(f64, f64, f64, ::powf); } diff --git a/src/sign.rs b/src/sign.rs index a0d6b0fd..ca6c1388 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -8,7 +8,7 @@ use crate::Num; pub trait Signed: Sized + Num + Neg { /// Computes the absolute value. /// - /// For `f32` and `f64`, `NaN` will be returned if the number is `NaN`. + /// For `f16`, `f32` and `f64`, `NaN` will be returned if the number is `NaN`. /// /// For signed integers, `::MIN` will be returned if the number is `::MIN`. fn abs(&self) -> Self; @@ -21,7 +21,7 @@ pub trait Signed: Sized + Num + Neg { /// Returns the sign of the number. /// - /// For `f32` and `f64`: + /// For `f16`, `f32` and `f64`: /// /// * `1.0` if the number is positive, `+0.0` or `INFINITY` /// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` @@ -150,12 +150,14 @@ macro_rules! signed_float_impl { }; } +#[cfg(has_f16)] +signed_float_impl!(f16); signed_float_impl!(f32); signed_float_impl!(f64); /// Computes the absolute value. /// -/// For `f32` and `f64`, `NaN` will be returned if the number is `NaN` +/// For `f16`, `f32` and `f64`, `NaN` will be returned if the number is `NaN` /// /// For signed integers, `::MIN` will be returned if the number is `::MIN`. #[inline(always)] @@ -174,7 +176,7 @@ pub fn abs_sub(x: T, y: T) -> T { /// Returns the sign of the number. /// -/// For `f32` and `f64`: +/// For `f16`, `f32` and `f64`: /// /// * `1.0` if the number is positive, `+0.0` or `INFINITY` /// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` From b7780e6d593fcb6e0a9b176ec1694b73a502b24f Mon Sep 17 00:00:00 2001 From: usamoi Date: Fri, 10 May 2024 18:47:43 +0800 Subject: [PATCH 2/3] add f128 --- build.rs | 2 + src/bounds.rs | 4 ++ src/cast.rs | 78 ++++++++++++++++++++----- src/float.rs | 140 +++++++++++++++++++++++++++++++++++++++++++-- src/identities.rs | 4 ++ src/lib.rs | 3 + src/ops/bytes.rs | 5 ++ src/ops/euclid.rs | 30 ++++++++++ src/ops/inv.rs | 16 ++++++ src/ops/mul_add.rs | 23 ++++++++ src/pow.rs | 18 ++++++ src/sign.rs | 10 ++-- 12 files changed, 308 insertions(+), 25 deletions(-) diff --git a/build.rs b/build.rs index a32ec3eb..477ae327 100644 --- a/build.rs +++ b/build.rs @@ -8,4 +8,6 @@ fn main() { // FIXME: use autocfg to emit it println!("cargo:rustc-cfg=has_f16"); println!("cargo:rustc-check-cfg=cfg(has_f16)"); + println!("cargo:rustc-cfg=has_f128"); + println!("cargo:rustc-check-cfg=cfg(has_f128)"); } diff --git a/src/bounds.rs b/src/bounds.rs index e283cb99..cc01455c 100644 --- a/src/bounds.rs +++ b/src/bounds.rs @@ -1,3 +1,5 @@ +#[cfg(has_f128)] +use core::f128; #[cfg(has_f16)] use core::f16; use core::num::Wrapping; @@ -83,6 +85,8 @@ impl Bounded for Wrapping { bounded_impl!(f16, f16::MIN, f16::MAX); bounded_impl!(f32, f32::MIN, f32::MAX); bounded_impl!(f64, f64::MIN, f64::MAX); +#[cfg(has_f128)] +bounded_impl!(f128, f128::MIN, f128::MAX); macro_rules! for_each_tuple_ { ( $m:ident !! ) => ( diff --git a/src/cast.rs b/src/cast.rs index cd45316e..704d4744 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -1,3 +1,5 @@ +#[cfg(has_f128)] +use core::f128; #[cfg(has_f16)] use core::f16; use core::mem::size_of; @@ -134,6 +136,15 @@ pub trait ToPrimitive { None => self.to_u64().as_ref().and_then(ToPrimitive::to_f64), } } + + /// Converts the value of `self` to an `f128`. Overflows may map to positive + /// or negative inifinity, otherwise `None` is returned if the value cannot + /// be represented by an `f128`. + #[cfg(has_f128)] + #[inline] + fn to_f128(&self) -> Option { + self.to_f64().as_ref().and_then(ToPrimitive::to_f128) + } } macro_rules! impl_to_primitive_int_to_int { @@ -201,6 +212,11 @@ macro_rules! impl_to_primitive_int { fn to_f64(&self) -> Option { Some(*self as f64) } + #[cfg(has_f128)] + #[inline] + fn to_f128(&self) -> Option { + Some(*self as f128) + } } }; } @@ -276,6 +292,11 @@ macro_rules! impl_to_primitive_uint { fn to_f64(&self) -> Option { Some(*self as f64) } + #[cfg(has_f128)] + #[inline] + fn to_f128(&self) -> Option { + Some(*self as f128) + } } }; } @@ -390,6 +411,8 @@ macro_rules! impl_to_primitive_float { fn to_f16 -> f16; fn to_f32 -> f32; fn to_f64 -> f64; + #[cfg(has_f128)] + fn to_f128 -> f128; } } }; @@ -399,6 +422,8 @@ macro_rules! impl_to_primitive_float { impl_to_primitive_float!(f16); impl_to_primitive_float!(f32); impl_to_primitive_float!(f64); +#[cfg(has_f128)] +impl_to_primitive_float!(f128); /// A generic trait for converting a number to a value. /// @@ -523,6 +548,14 @@ pub trait FromPrimitive: Sized { None => n.to_u64().and_then(FromPrimitive::from_u64), } } + + /// Converts a `f128` to return an optional value of this type. If the + /// value cannot be represented by this type, then `None` is returned. + #[cfg(has_f128)] + #[inline] + fn from_f128(n: f128) -> Option { + FromPrimitive::from_f64(n as f64) + } } macro_rules! impl_from_primitive { @@ -592,6 +625,11 @@ macro_rules! impl_from_primitive { fn from_f64(n: f64) -> Option<$T> { n.$to_ty() } + #[cfg(has_f128)] + #[inline] + fn from_f128(n: f128) -> Option<$T> { + n.$to_ty() + } } }; } @@ -612,6 +650,8 @@ impl_from_primitive!(u128, to_u128); impl_from_primitive!(f16, to_f16); impl_from_primitive!(f32, to_f32); impl_from_primitive!(f64, to_f64); +#[cfg(has_f128)] +impl_from_primitive!(f128, to_f128); macro_rules! impl_to_primitive_wrapping { ($( $(#[$cfg:meta])* fn $method:ident -> $i:ident ; )*) => {$( @@ -643,6 +683,8 @@ impl ToPrimitive for Wrapping { fn to_f16 -> f16; fn to_f32 -> f32; fn to_f64 -> f64; + #[cfg(has_f128)] + fn to_f128 -> f128; } } @@ -676,6 +718,8 @@ impl FromPrimitive for Wrapping { fn from_f16(f16); fn from_f32(f32); fn from_f64(f64); + #[cfg(has_f128)] + fn from_f128(f128); } } @@ -741,6 +785,8 @@ impl_num_cast!(isize, to_isize); impl_num_cast!(f16, to_f16); impl_num_cast!(f32, to_f32); impl_num_cast!(f64, to_f64); +#[cfg(has_f128)] +impl_num_cast!(f128, to_f128); impl NumCast for Wrapping { fn from(n: U) -> Option { @@ -799,21 +845,23 @@ macro_rules! impl_as_primitive { }; } -impl_as_primitive!(u8 => { char, #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(i8 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(u16 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(i16 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(u32 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(i32 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(u64 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(i64 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(u128 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(i128 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(usize => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(isize => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(u8 => { char, #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(i8 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(u16 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(i16 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(u32 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(i32 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(u64 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(i64 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(u128 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(i128 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(usize => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(isize => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); #[cfg(has_f16)] -impl_as_primitive!(f16 => { f16, f32, f64 }); -impl_as_primitive!(f32 => { #[cfg(has_f16)] f16, f32, f64 }); -impl_as_primitive!(f64 => { #[cfg(has_f16)] f16, f32, f64 }); +impl_as_primitive!(f16 => { f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(f32 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +impl_as_primitive!(f64 => { #[cfg(has_f16)] f16, f32, f64, #[cfg(has_f128)] f128 }); +#[cfg(has_f128)] +impl_as_primitive!(f128 => { #[cfg(has_f16)] f16, f32, f64, f128 }); impl_as_primitive!(char => { char }); impl_as_primitive!(bool => {}); diff --git a/src/float.rs b/src/float.rs index 2bca5d26..1c41e1d4 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2,6 +2,8 @@ use core::cmp::Ordering; use core::num::FpCategory; use core::ops::{Add, Div, Neg}; +#[cfg(has_f128)] +use core::f128; #[cfg(has_f16)] use core::f16; use core::f32; @@ -779,6 +781,10 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { /// Returns the mantissa, base 2 exponent, and sign as integers, respectively. /// The original number can be recovered by `sign * mantissa * 2 ^ exponent`. /// + /// Note: For `f128`, this function is lossy. The original `f128` value cannot + /// be exactly recovered by `sign * mantissa * 2 ^ exponent` due to the precision + /// limitation of 64-bit mantissa. + /// /// # Examples /// /// ``` @@ -974,6 +980,56 @@ impl FloatCore for f64 { } } +#[cfg(has_f128)] +impl FloatCore for f128 { + constant! { + infinity() -> f128::INFINITY; + neg_infinity() -> f128::NEG_INFINITY; + nan() -> f128::NAN; + neg_zero() -> -0.0; + min_value() -> f128::MIN; + min_positive_value() -> f128::MIN_POSITIVE; + epsilon() -> f128::EPSILON; + max_value() -> f128::MAX; + } + + #[inline] + fn integer_decode(self) -> (u64, i16, i8) { + integer_decode_f128_truncated(self) + } + + forward! { + Self::is_nan(self) -> bool; + Self::is_infinite(self) -> bool; + Self::is_finite(self) -> bool; + Self::is_normal(self) -> bool; + Self::is_subnormal(self) -> bool; + Self::classify(self) -> FpCategory; + Self::is_sign_positive(self) -> bool; + Self::is_sign_negative(self) -> bool; + Self::min(self, other: Self) -> Self; + Self::max(self, other: Self) -> Self; + Self::recip(self) -> Self; + Self::to_degrees(self) -> Self; + Self::to_radians(self) -> Self; + } + + #[cfg(feature = "std")] + forward! { + Self::floor(self) -> Self; + Self::ceil(self) -> Self; + Self::round(self) -> Self; + Self::trunc(self) -> Self; + Self::fract(self) -> Self; + Self::abs(self) -> Self; + Self::signum(self) -> Self; + Self::powi(self, n: i32) -> Self; + } + + // TODO: + // use floor, ceil, round, trunc, abs and fract provided by libm +} + // FIXME: these doctests aren't actually helpful, because they're using and // testing the inherent methods directly, not going through `Float`. @@ -2144,6 +2200,27 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) { (mantissa, exponent, sign) } +#[cfg(has_f128)] +fn integer_decode_f128(f: f128) -> (u128, i16, i8) { + let bits: u128 = f.to_bits(); + let sign: i8 = if bits >> 127 == 0 { 1 } else { -1 }; + let mut exponent: i16 = ((bits >> 112) & 0x7fff) as i16; + let mantissa = if exponent == 0 { + (bits & 0xffffffffffffffffffffffffffff) << 1 + } else { + (bits & 0xffffffffffffffffffffffffffff) | 0x10000000000000000000000000000 + }; + // Exponent bias + mantissa shift + exponent -= 16383 + 112; + (mantissa, exponent, sign) +} + +#[cfg(has_f128)] +fn integer_decode_f128_truncated(f: f128) -> (u64, i16, i8) { + let (mantissa, exponent, sign) = integer_decode_f128(f); + ((mantissa >> (113 - 64)) as u64, exponent + (113 - 64), sign) +} + #[cfg(has_f16)] #[cfg(feature = "std")] float_impl_std!(f16 integer_decode_f16); @@ -2151,6 +2228,9 @@ float_impl_std!(f16 integer_decode_f16); float_impl_std!(f32 integer_decode_f32); #[cfg(feature = "std")] float_impl_std!(f64 integer_decode_f64); +#[cfg(has_f128)] +#[cfg(feature = "std")] +float_impl_std!(f128 integer_decode_f128_truncated); #[cfg(all(not(feature = "std"), feature = "libm"))] impl Float for f32 { @@ -2269,6 +2349,8 @@ macro_rules! float_const_impl { float_const_impl! { @float f16, $($constant,)+ } float_const_impl! { @float f32, $($constant,)+ } float_const_impl! { @float f64, $($constant,)+ } + #[cfg(has_f128)] + float_const_impl! { @float f128, $($constant,)+ } ); (@float $T:ident, $($constant:ident,)+) => ( impl FloatConst for $T { @@ -2401,6 +2483,8 @@ totalorder_impl!(f64, i64, u64, 64); totalorder_impl!(f32, i32, u32, 32); #[cfg(has_f16)] totalorder_impl!(f16, i16, u16, 16); +#[cfg(has_f128)] +totalorder_impl!(f128, i16, u16, 16); #[cfg(test)] mod tests { @@ -2424,9 +2508,11 @@ mod tests { assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-6); assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-6); - let (deg, rad) = (deg as f32, rad as f32); - assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-5); - assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-5); + { + let (deg, rad) = (deg as f32, rad as f32); + assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-5); + assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-5); + } #[cfg(has_f16)] { @@ -2434,6 +2520,13 @@ mod tests { assert!((FloatCore::to_degrees(rad) - deg).abs() < 1.0); assert!((FloatCore::to_radians(deg) - rad).abs() < 0.01); } + + #[cfg(has_f128)] + { + let (deg, rad) = (deg as f128, rad as f128); + assert!((FloatCore::to_degrees(rad) - deg).abs() < 1e-7); + assert!((FloatCore::to_radians(deg) - rad).abs() < 1e-7); + } } } @@ -2446,9 +2539,11 @@ mod tests { assert!((Float::to_degrees(rad) - deg).abs() < 1e-6); assert!((Float::to_radians(deg) - rad).abs() < 1e-6); - let (deg, rad) = (deg as f32, rad as f32); - assert!((Float::to_degrees(rad) - deg).abs() < 1e-5); - assert!((Float::to_radians(deg) - rad).abs() < 1e-5); + { + let (deg, rad) = (deg as f32, rad as f32); + assert!((Float::to_degrees(rad) - deg).abs() < 1e-5); + assert!((Float::to_radians(deg) - rad).abs() < 1e-5); + } #[cfg(has_f16)] { @@ -2456,6 +2551,13 @@ mod tests { assert!((Float::to_degrees(rad) - deg).abs() < 1.0); assert!((Float::to_radians(deg) - rad).abs() < 0.01); } + + #[cfg(has_f128)] + { + let (deg, rad) = (deg as f128, rad as f128); + assert!((Float::to_degrees(rad) - deg).abs() < 1e-7); + assert!((Float::to_radians(deg) - rad).abs() < 1e-7); + } } } @@ -2488,6 +2590,8 @@ mod tests { check::(1e-2); check::(1e-6); check::(1e-12); + #[cfg(has_f128)] + check::(1e-24); } #[test] @@ -2498,6 +2602,8 @@ mod tests { test_copysign_generic(2.0_f16, -2.0_f16, f16::nan()); test_copysign_generic(2.0_f32, -2.0_f32, f32::nan()); test_copysign_generic(2.0_f64, -2.0_f64, f64::nan()); + #[cfg(has_f128)] + test_copysign_generic(2.0_f128, -2.0_f128, f128::nan()); test_copysignf(2.0_f32, -2.0_f32, f32::nan()); } @@ -2548,6 +2654,8 @@ mod tests { #[test] #[cfg(any(feature = "std", feature = "libm"))] fn subnormal() { + #[cfg(has_f128)] + test_subnormal::(); test_subnormal::(); test_subnormal::(); #[cfg(has_f16)] @@ -2570,11 +2678,15 @@ mod tests { assert_eq!(x.total_cmp(&y), Ordering::Greater); } + #[cfg(has_f128)] + check_eq(f128::NAN, f128::NAN); check_eq(f64::NAN, f64::NAN); check_eq(f32::NAN, f32::NAN); #[cfg(has_f16)] check_eq(f16::NAN, f16::NAN); + #[cfg(has_f128)] + check_lt(-0.0_f128, 0.0_f128); check_lt(-0.0_f64, 0.0_f64); check_lt(-0.0_f32, 0.0_f32); #[cfg(has_f16)] @@ -2584,6 +2696,14 @@ mod tests { // https://github.com/rust-lang/rust/issues/115567 #[cfg(not(target_arch = "x86"))] { + let s_nan = f128::from_bits(0x7ffe8000000000000000000000000000); + let q_nan = f128::from_bits(0x7fff8000000000000000000000000000); + check_lt(s_nan, q_nan); + + let neg_s_nan = f128::from_bits(0xfffe8000000000000000000000000000); + let neg_q_nan = f128::from_bits(0xffff8000000000000000000000000000); + check_lt(neg_q_nan, neg_s_nan); + let s_nan = f64::from_bits(0x7ff4000000000000); let q_nan = f64::from_bits(0x7ff8000000000000); check_lt(s_nan, q_nan); @@ -2609,6 +2729,14 @@ mod tests { check_lt(neg_q_nan, neg_s_nan); } + #[cfg(has_f128)] + { + check_lt(-f128::NAN, f128::NEG_INFINITY); + check_gt(1.0_f128, -f128::NAN); + check_lt(f128::INFINITY, f128::NAN); + check_gt(f128::NAN, 1.0_f128); + } + check_lt(-f64::NAN, f64::NEG_INFINITY); check_gt(1.0_f64, -f64::NAN); check_lt(f64::INFINITY, f64::NAN); diff --git a/src/identities.rs b/src/identities.rs index 0575cbe3..d77039fa 100644 --- a/src/identities.rs +++ b/src/identities.rs @@ -72,6 +72,8 @@ zero_impl!(i128, 0); zero_impl!(f16, 0.0); zero_impl!(f32, 0.0); zero_impl!(f64, 0.0); +#[cfg(has_f128)] +zero_impl!(f128, 0.0); impl Zero for Wrapping where @@ -179,6 +181,8 @@ one_impl!(i128, 1); one_impl!(f16, 1.0); one_impl!(f32, 1.0); one_impl!(f64, 1.0); +#[cfg(has_f128)] +one_impl!(f128, 1.0); impl One for Wrapping where diff --git a/src/lib.rs b/src/lib.rs index f350a469..9a62b810 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ #![deny(unconditional_recursion)] #![no_std] #![cfg_attr(has_f16, feature(f16))] // FIXME: remove it when it's stablized +#![cfg_attr(has_f128, feature(f128))] // FIXME: remove it when it's stablized // Need to explicitly bring the crate in for inherent float methods #[cfg(feature = "std")] @@ -556,6 +557,8 @@ macro_rules! float_trait_impl { #[cfg(has_f16)] float_trait_impl!(Num for f16); float_trait_impl!(Num for f32 f64); +#[cfg(has_f128)] +float_trait_impl!(no_fast_path Num for f128); /// A value bounded by a minimum and a maximum /// diff --git a/src/ops/bytes.rs b/src/ops/bytes.rs index 7b9cab15..0435bfa6 100644 --- a/src/ops/bytes.rs +++ b/src/ops/bytes.rs @@ -256,6 +256,8 @@ int_to_from_bytes_impl!(isize, 4); float_to_from_bytes_impl!(f16, 2); float_to_from_bytes_impl!(f32, 4); float_to_from_bytes_impl!(f64, 8); +#[cfg(has_f128)] +float_to_from_bytes_impl!(f128, 16); #[cfg(test)] mod tests { @@ -318,5 +320,8 @@ mod tests { check_to_from_bytes!(f16); check_to_from_bytes!(f32 f64); + + #[cfg(has_f128)] + check_to_from_bytes!(f128); } } diff --git a/src/ops/euclid.rs b/src/ops/euclid.rs index eaf32308..ae406e35 100644 --- a/src/ops/euclid.rs +++ b/src/ops/euclid.rs @@ -95,6 +95,10 @@ euclid_forward_impl!(f16); #[cfg(feature = "std")] euclid_forward_impl!(f32 f64); +#[cfg(has_f128)] +#[cfg(feature = "std")] +euclid_forward_impl!(f128); + #[cfg(has_f16)] #[cfg(not(feature = "std"))] impl Euclid for f16 { @@ -162,6 +166,29 @@ impl Euclid for f64 { } } +#[cfg(has_f128)] +#[cfg(not(feature = "std"))] +impl Euclid for f128 { + #[inline] + fn div_euclid(&self, v: &f128) -> f128 { + let q = ::trunc(self / v); + if self % v < 0.0 { + return if *v > 0.0 { q - 1.0 } else { q + 1.0 }; + } + q + } + + #[inline] + fn rem_euclid(&self, v: &f128) -> f128 { + let r = self % v; + if r < 0.0 { + r + ::abs(*v) + } else { + r + } + } +} + pub trait CheckedEuclid: Euclid { /// Performs euclid division, returning `None` on division by zero or if /// overflow occurred. @@ -287,6 +314,9 @@ mod tests { test_euclid!(f16); test_euclid!(f32 f64); + + #[cfg(has_f128)] + test_euclid!(f128); } #[test] diff --git a/src/ops/inv.rs b/src/ops/inv.rs index 17076e5e..f00e99d4 100644 --- a/src/ops/inv.rs +++ b/src/ops/inv.rs @@ -39,6 +39,14 @@ impl Inv for f64 { 1.0 / self } } +#[cfg(has_f128)] +impl Inv for f128 { + type Output = f128; + #[inline] + fn inv(self) -> f128 { + 1.0 / self + } +} #[cfg(has_f16)] impl<'a> Inv for &'a f16 { type Output = f16; @@ -61,3 +69,11 @@ impl<'a> Inv for &'a f64 { 1.0 / *self } } +#[cfg(has_f128)] +impl<'a> Inv for &'a f128 { + type Output = f128; + #[inline] + fn inv(self) -> f128 { + 1.0 / *self + } +} diff --git a/src/ops/mul_add.rs b/src/ops/mul_add.rs index b60656cd..acfc5ec8 100644 --- a/src/ops/mul_add.rs +++ b/src/ops/mul_add.rs @@ -65,6 +65,17 @@ impl MulAdd for f64 { } } +#[cfg(has_f128)] +#[cfg(any(feature = "std", feature = "libm"))] +impl MulAdd for f128 { + type Output = Self; + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self::Output { + ::mul_add(self, a, b) + } +} + macro_rules! mul_add_impl { ($trait_name:ident for $($t:ty)*) => {$( impl $trait_name for $t { @@ -106,6 +117,15 @@ impl MulAddAssign for f64 { } } +#[cfg(has_f128)] +#[cfg(any(feature = "std", feature = "libm"))] +impl MulAddAssign for f128 { + #[inline] + fn mul_add_assign(&mut self, a: Self, b: Self) { + *self = ::mul_add(*self, a, b) + } +} + macro_rules! mul_add_assign_impl { ($trait_name:ident for $($t:ty)*) => {$( impl $trait_name for $t { @@ -168,5 +188,8 @@ mod tests { test_mul_add!(f16); test_mul_add!(f32 f64); + + #[cfg(has_f128)] + test_mul_add!(f128); } } diff --git a/src/pow.rs b/src/pow.rs index fc993903..f2a3ecb1 100644 --- a/src/pow.rs +++ b/src/pow.rs @@ -202,6 +202,16 @@ mod float_impls { pow_impl!(f64, i16, i32, ::powi); pow_impl!(f64, u16, i32, ::powi); pow_impl!(f64, i32, i32, ::powi); + #[cfg(has_f128)] + pow_impl!(f128, i8, i32, ::powi); + #[cfg(has_f128)] + pow_impl!(f128, u8, i32, ::powi); + #[cfg(has_f128)] + pow_impl!(f128, i16, i32, ::powi); + #[cfg(has_f128)] + pow_impl!(f128, u16, i32, ::powi); + #[cfg(has_f128)] + pow_impl!(f128, i32, i32, ::powi); #[cfg(has_f16)] pow_impl!(f16, f16, f16, ::powf); #[cfg(has_f16)] @@ -211,6 +221,14 @@ mod float_impls { pow_impl!(f64, f16, f64, ::powf); pow_impl!(f64, f32, f64, ::powf); pow_impl!(f64, f64, f64, ::powf); + #[cfg(all(has_f16, has_f128))] + pow_impl!(f128, f16, f128, ::powf); + #[cfg(has_f128)] + pow_impl!(f128, f32, f128, ::powf); + #[cfg(has_f128)] + pow_impl!(f128, f64, f128, ::powf); + #[cfg(has_f128)] + pow_impl!(f128, f128, f128, ::powf); } /// Raises a value to the power of exp, using exponentiation by squaring. diff --git a/src/sign.rs b/src/sign.rs index ca6c1388..306270e7 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -8,7 +8,7 @@ use crate::Num; pub trait Signed: Sized + Num + Neg { /// Computes the absolute value. /// - /// For `f16`, `f32` and `f64`, `NaN` will be returned if the number is `NaN`. + /// For `f16`, `f32`, `f64` and `f128`, `NaN` will be returned if the number is `NaN`. /// /// For signed integers, `::MIN` will be returned if the number is `::MIN`. fn abs(&self) -> Self; @@ -21,7 +21,7 @@ pub trait Signed: Sized + Num + Neg { /// Returns the sign of the number. /// - /// For `f16`, `f32` and `f64`: + /// For `f16`, `f32`, `f64` and `f128`: /// /// * `1.0` if the number is positive, `+0.0` or `INFINITY` /// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` @@ -154,10 +154,12 @@ macro_rules! signed_float_impl { signed_float_impl!(f16); signed_float_impl!(f32); signed_float_impl!(f64); +#[cfg(has_f128)] +signed_float_impl!(f128); /// Computes the absolute value. /// -/// For `f16`, `f32` and `f64`, `NaN` will be returned if the number is `NaN` +/// For `f16`, `f32`, `f64` and `f128`, `NaN` will be returned if the number is `NaN` /// /// For signed integers, `::MIN` will be returned if the number is `::MIN`. #[inline(always)] @@ -176,7 +178,7 @@ pub fn abs_sub(x: T, y: T) -> T { /// Returns the sign of the number. /// -/// For `f16`, `f32` and `f64`: +/// For `f16`, `f32`, `f64` and `f128`: /// /// * `1.0` if the number is positive, `+0.0` or `INFINITY` /// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` From 3deee502015b67f5b7df2a79650a0c1228a8e1d3 Mon Sep 17 00:00:00 2001 From: usamoi Date: Sat, 31 May 2025 23:14:48 +0800 Subject: [PATCH 3/3] fix no_std + libm --- Cargo.toml | 2 +- src/float.rs | 150 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/macros.rs | 17 ++++++ 3 files changed, 164 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 226d4164..c055bc73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ features = ["std"] rustdoc-args = ["--generate-link-to-definition"] [dependencies] -libm = { version = "0.2.0", optional = true } +libm = { version = "0.2.0", optional = true, features = ["unstable-float"] } [features] default = ["std"] diff --git a/src/float.rs b/src/float.rs index 1c41e1d4..6d1ccc4e 100644 --- a/src/float.rs +++ b/src/float.rs @@ -852,8 +852,20 @@ impl FloatCore for f16 { Self::powi(self, n: i32) -> Self; } - // TODO: - // use floor, ceil, round, trunc, abs and fract provided by libm + #[cfg(all(not(feature = "std"), feature = "libm"))] + forward! { + libm::floorf16 as floor(self) -> Self; + libm::ceilf16 as ceil(self) -> Self; + libm::roundf16 as round(self) -> Self; + libm::truncf16 as trunc(self) -> Self; + libm::fabsf16 as abs(self) -> Self; + } + + #[cfg(all(not(feature = "std"), feature = "libm"))] + #[inline] + fn fract(self) -> Self { + self - libm::truncf16(self) + } } impl FloatCore for f32 { @@ -1026,8 +1038,24 @@ impl FloatCore for f128 { Self::powi(self, n: i32) -> Self; } - // TODO: - // use floor, ceil, round, trunc, abs and fract provided by libm + #[cfg(all(not(feature = "std"), feature = "libm"))] + forward! { + libm::floorf128 as floor(self) -> Self; + libm::ceilf128 as ceil(self) -> Self; + libm::roundf128 as round(self) -> Self; + libm::truncf128 as trunc(self) -> Self; + } + + #[cfg(all(not(feature = "std"), feature = "libm"))] + forward! { + f128::abs as abs(self) -> Self; + } + + #[cfg(all(not(feature = "std"), feature = "libm"))] + #[inline] + fn fract(self) -> Self { + self - libm::truncf128(self) + } } // FIXME: these doctests aren't actually helpful, because they're using and @@ -2232,6 +2260,63 @@ float_impl_std!(f64 integer_decode_f64); #[cfg(feature = "std")] float_impl_std!(f128 integer_decode_f128_truncated); +#[cfg(all(not(feature = "std"), feature = "libm"))] +impl Float for f16 { + float_impl_libm!(f16 integer_decode_f16); + + #[inline] + #[allow(deprecated)] + fn abs_sub(self, other: Self) -> Self { + libm::fdimf16(self, other) + } + + forward! { + libm::floorf16 as floor(self) -> Self; + libm::ceilf16 as ceil(self) -> Self; + libm::roundf16 as round(self) -> Self; + libm::truncf16 as trunc(self) -> Self; + } + + cast_forward_cast! { + [f32] libm::fmaf as mul_add(self, a: Self, b: Self) -> Self; + [f32] libm::powf as powf(self, n: Self) -> Self; + [f32] libm::sqrtf as sqrt(self) -> Self; + [f32] libm::expf as exp(self) -> Self; + [f32] libm::exp2f as exp2(self) -> Self; + [f32] libm::logf as ln(self) -> Self; + [f32] libm::log2f as log2(self) -> Self; + [f32] libm::log10f as log10(self) -> Self; + [f32] libm::cbrtf as cbrt(self) -> Self; + [f32] libm::hypotf as hypot(self, other: Self) -> Self; + [f32] libm::sinf as sin(self) -> Self; + [f32] libm::cosf as cos(self) -> Self; + [f32] libm::tanf as tan(self) -> Self; + [f32] libm::asinf as asin(self) -> Self; + [f32] libm::acosf as acos(self) -> Self; + [f32] libm::atanf as atan(self) -> Self; + [f32] libm::atan2f as atan2(self, other: Self) -> Self; + [f32] libm::expm1f as exp_m1(self) -> Self; + [f32] libm::log1pf as ln_1p(self) -> Self; + [f32] libm::sinhf as sinh(self) -> Self; + [f32] libm::coshf as cosh(self) -> Self; + [f32] libm::tanhf as tanh(self) -> Self; + [f32] libm::asinhf as asinh(self) -> Self; + [f32] libm::acoshf as acosh(self) -> Self; + [f32] libm::atanhf as atanh(self) -> Self; + } + + forward! { + f16::abs as abs(self) -> Self; + f16::copysign as copysign(self, other: Self) -> Self; + } + + #[inline] + fn sin_cos(self) -> (Self, Self) { + let (x, y) = libm::sincosf(self as f32); + (x as Self, y as Self) + } +} + #[cfg(all(not(feature = "std"), feature = "libm"))] impl Float for f32 { float_impl_libm!(f32 integer_decode_f32); @@ -2324,6 +2409,63 @@ impl Float for f64 { } } +#[cfg(all(not(feature = "std"), feature = "libm"))] +impl Float for f128 { + float_impl_libm!(f128 integer_decode_f128_truncated); + + #[inline] + #[allow(deprecated)] + fn abs_sub(self, other: Self) -> Self { + libm::fdimf128(self, other) + } + + forward! { + libm::floorf128 as floor(self) -> Self; + libm::ceilf128 as ceil(self) -> Self; + libm::roundf128 as round(self) -> Self; + libm::truncf128 as trunc(self) -> Self; + } + + cast_forward_cast! { + [f64] libm::pow as powf(self, n: Self) -> Self; + [f64] libm::exp as exp(self) -> Self; + [f64] libm::exp2 as exp2(self) -> Self; + [f64] libm::log as ln(self) -> Self; + [f64] libm::log2 as log2(self) -> Self; + [f64] libm::log10 as log10(self) -> Self; + [f64] libm::cbrt as cbrt(self) -> Self; + [f64] libm::hypot as hypot(self, other: Self) -> Self; + [f64] libm::sin as sin(self) -> Self; + [f64] libm::cos as cos(self) -> Self; + [f64] libm::tan as tan(self) -> Self; + [f64] libm::asin as asin(self) -> Self; + [f64] libm::acos as acos(self) -> Self; + [f64] libm::atan as atan(self) -> Self; + [f64] libm::atan2 as atan2(self, other: Self) -> Self; + [f64] libm::expm1 as exp_m1(self) -> Self; + [f64] libm::log1p as ln_1p(self) -> Self; + [f64] libm::sinh as sinh(self) -> Self; + [f64] libm::cosh as cosh(self) -> Self; + [f64] libm::tanh as tanh(self) -> Self; + [f64] libm::asinh as asinh(self) -> Self; + [f64] libm::acosh as acosh(self) -> Self; + [f64] libm::atanh as atanh(self) -> Self; + } + + forward! { + libm::fmaf128 as mul_add(self, a: Self, b: Self) -> Self; + libm::sqrtf128 as sqrt(self) -> Self; + f128::abs as abs(self) -> Self; + f128::copysign as copysign(self, other: Self) -> Self; + } + + #[inline] + fn sin_cos(self) -> (Self, Self) { + let (x, y) = libm::sincos(self as f64); + (x as Self, y as Self) + } +} + macro_rules! float_const_impl { ($(#[$doc:meta] $constant:ident,)+) => ( #[allow(non_snake_case)] diff --git a/src/macros.rs b/src/macros.rs index b97758e4..3a6faab4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,6 +3,13 @@ /// Forward a method to an inherent method or a base trait method. macro_rules! forward { + ($( unsafe $imp:path as $method:ident ( self $( , $arg:ident : $ty:ty )* ) -> $ret:ty ; )*) + => {$( + #[inline] + fn $method(self $( , $arg : $ty )* ) -> $ret { + unsafe { $imp(self $( , $arg )* ) } + } + )*}; ($( Self :: $method:ident ( self $( , $arg:ident : $ty:ty )* ) -> $ret:ty ; )*) => {$( #[inline] @@ -33,6 +40,16 @@ macro_rules! forward { )*}; } +macro_rules! cast_forward_cast { + ($( [$cast:ty] $imp:path as $method:ident ( self $( , $arg:ident : $ty:ty )* ) -> $ret:ty ; )*) + => {$( + #[inline] + fn $method(self $( , $arg : $ty )* ) -> $ret { + $imp(self as $cast $( , $arg as $cast)* ) as $ret + } + )*}; +} + macro_rules! constant { ($( $method:ident () -> $ret:expr ; )*) => {$(