Auto merge of #78618 - workingjubilee:ieee754-fmt, r=m-ou-se

Add IEEE 754 compliant fmt/parse of -0, infinity, NaN

This pull request improves the Rust float formatting/parsing libraries to comply with IEEE 754's formatting expectations around certain special values, namely signed zero, the infinities, and NaN. It also adds IEEE 754 compliance tests that, while less stringent in certain places than many of the existing flt2dec/dec2flt capability tests, are intended to serve as the beginning of a roadmap to future compliance with the standard. Some relevant documentation is also adjusted with clarifying remarks.

This PR follows from discussion in https://github.com/rust-lang/rfcs/issues/1074, and closes #24623.

The most controversial change here is likely to be that -0 is now printed as -0. Allow me to explain: While there appears to be community support for an opt-in toggle of printing floats as if they exist in the naively expected domain of numbers, i.e. not the extended reals (where floats live), IEEE 754-2019 is clear that a float converted to a string should be capable of being transformed into the original floating point bit-pattern when it satisfies certain conditions (namely, when it is an actual numeric value i.e. not a NaN and the original and destination float width are the same). -0 is given special attention here as a value that should have its sign preserved. In addition, the vast majority of other programming languages not only output `-0` but output `-0.0` here.

While IEEE 754 offers a broad leeway in how to handle producing what it calls a "decimal character sequence", it is clear that the operations a language provides should be capable of round tripping, and it is confusing to advertise the f32 and f64 types as binary32 and binary64 yet have the most basic way of producing a string and then reading it back into a floating point number be non-conformant with the standard. Further, existing documentation suggested that e.g. -0 would be printed with -0 regardless of the presence of the `+` fmt character, but it prints "+0" instead if given such (which was what led to the opening of #24623).

There are other parsing and formatting issues for floating point numbers which prevent Rust from complying with the standard, as well as other well-documented challenges on the arithmetic level, but I hope that this can be the beginning of motion towards solving those challenges.
This commit is contained in:
bors 2021-03-27 10:40:16 +00:00
commit aef11409b4
10 changed files with 275 additions and 211 deletions

View File

@ -161,9 +161,8 @@
//!
//! * `+` - This is intended for numeric types and indicates that the sign
//! should always be printed. Positive signs are never printed by
//! default, and the negative sign is only printed by default for the
//! `Signed` trait. This flag indicates that the correct sign (`+` or `-`)
//! should always be printed.
//! default, and the negative sign is only printed by default for signed values.
//! This flag indicates that the correct sign (`+` or `-`) should always be printed.
//! * `-` - Currently not used
//! * `#` - This flag indicates that the "alternate" form of printing should
//! be used. The alternate forms are:

View File

@ -151,8 +151,7 @@ fn test_format_macro_interface() {
t!(format!("{:+10.3e}", -1.2345e6f64), " -1.234e6");
// Float edge cases
t!(format!("{}", -0.0), "0");
t!(format!("{:?}", -0.0), "-0.0");
t!(format!("{}", -0.0), "-0");
t!(format!("{:?}", 0.0), "0.0");
// sign aware zero padding

View File

@ -54,21 +54,14 @@ where
}
// Common code of floating point Debug and Display.
fn float_to_decimal_common<T>(
fmt: &mut Formatter<'_>,
num: &T,
negative_zero: bool,
min_precision: usize,
) -> Result
fn float_to_decimal_common<T>(fmt: &mut Formatter<'_>, num: &T, min_precision: usize) -> Result
where
T: flt2dec::DecodableFloat,
{
let force_sign = fmt.sign_plus();
let sign = match (force_sign, negative_zero) {
(false, false) => flt2dec::Sign::Minus,
(false, true) => flt2dec::Sign::MinusRaw,
(true, false) => flt2dec::Sign::MinusPlus,
(true, true) => flt2dec::Sign::MinusPlusRaw,
let sign = match force_sign {
false => flt2dec::Sign::Minus,
true => flt2dec::Sign::MinusPlus,
};
if let Some(precision) = fmt.precision {
@ -156,14 +149,14 @@ macro_rules! floating {
#[stable(feature = "rust1", since = "1.0.0")]
impl Debug for $ty {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
float_to_decimal_common(fmt, self, true, 1)
float_to_decimal_common(fmt, self, 1)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Display for $ty {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
float_to_decimal_common(fmt, self, false, 0)
float_to_decimal_common(fmt, self, 0)
}
}

View File

@ -239,13 +239,15 @@ fn dec2flt<T: RawFloat>(s: &str) -> Result<T, ParseFloatError> {
ParseResult::Valid(decimal) => convert(decimal)?,
ParseResult::ShortcutToInf => T::INFINITY,
ParseResult::ShortcutToZero => T::ZERO,
ParseResult::Invalid => match s {
"inf" => T::INFINITY,
"NaN" => T::NAN,
_ => {
ParseResult::Invalid => {
if s.eq_ignore_ascii_case("nan") {
T::NAN
} else if s.eq_ignore_ascii_case("inf") || s.eq_ignore_ascii_case("infinity") {
T::INFINITY
} else {
return Err(pfe_invalid());
}
},
}
};
match sign {

View File

@ -399,14 +399,10 @@ fn digits_to_exp_str<'a>(
/// Sign formatting options.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Sign {
/// Prints `-` only for the negative non-zero values.
Minus, // -inf -1 0 0 1 inf nan
/// Prints `-` only for any negative values (including the negative zero).
MinusRaw, // -inf -1 -0 0 1 inf nan
/// Prints `-` for the negative non-zero values, or `+` otherwise.
MinusPlus, // -inf -1 +0 +0 +1 +inf nan
/// Prints `-` for any negative values (including the negative zero), or `+` otherwise.
MinusPlusRaw, // -inf -1 -0 +0 +1 +inf nan
/// Prints `-` for any negative value.
Minus, // -inf -1 -0 0 1 inf nan
/// Prints `-` for any negative value, or `+` otherwise.
MinusPlus, // -inf -1 -0 +0 +1 +inf nan
}
/// Returns the static byte string corresponding to the sign to be formatted.
@ -414,30 +410,14 @@ pub enum Sign {
fn determine_sign(sign: Sign, decoded: &FullDecoded, negative: bool) -> &'static str {
match (*decoded, sign) {
(FullDecoded::Nan, _) => "",
(FullDecoded::Zero, Sign::Minus) => "",
(FullDecoded::Zero, Sign::MinusRaw) => {
(_, Sign::Minus) => {
if negative {
"-"
} else {
""
}
}
(FullDecoded::Zero, Sign::MinusPlus) => "+",
(FullDecoded::Zero, Sign::MinusPlusRaw) => {
if negative {
"-"
} else {
"+"
}
}
(_, Sign::Minus | Sign::MinusRaw) => {
if negative {
"-"
} else {
""
}
}
(_, Sign::MinusPlus | Sign::MinusPlusRaw) => {
(_, Sign::MinusPlus) => {
if negative {
"-"
} else {

View File

@ -514,51 +514,38 @@ where
let f = &mut f_;
assert_eq!(to_string(f, 0.0, Minus, 0), "0");
assert_eq!(to_string(f, 0.0, MinusRaw, 0), "0");
assert_eq!(to_string(f, 0.0, Minus, 0), "0");
assert_eq!(to_string(f, 0.0, MinusPlus, 0), "+0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 0), "+0");
assert_eq!(to_string(f, -0.0, Minus, 0), "0");
assert_eq!(to_string(f, -0.0, MinusRaw, 0), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, 0), "+0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 0), "-0");
assert_eq!(to_string(f, -0.0, Minus, 0), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, 0), "-0");
assert_eq!(to_string(f, 0.0, Minus, 1), "0.0");
assert_eq!(to_string(f, 0.0, Minus, 1), "0.0");
assert_eq!(to_string(f, 0.0, MinusRaw, 1), "0.0");
assert_eq!(to_string(f, 0.0, MinusPlus, 1), "+0.0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 1), "+0.0");
assert_eq!(to_string(f, -0.0, Minus, 8), "0.00000000");
assert_eq!(to_string(f, -0.0, MinusRaw, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlus, 8), "+0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, Minus, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlus, 8), "-0.00000000");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusRaw, 0), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 0), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlusRaw, 0), "+inf");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 0), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusRaw, 1), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 8), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlusRaw, 64), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 1), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 64), "NaN");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 0), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusRaw, 1), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 8), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlusRaw, 64), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 1), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64), "-inf");
assert_eq!(to_string(f, 3.14, Minus, 0), "3.14");
assert_eq!(to_string(f, 3.14, MinusRaw, 0), "3.14");
assert_eq!(to_string(f, 3.14, Minus, 0), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, 0), "+3.14");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 0), "+3.14");
assert_eq!(to_string(f, -3.14, Minus, 0), "-3.14");
assert_eq!(to_string(f, -3.14, MinusRaw, 0), "-3.14");
assert_eq!(to_string(f, -3.14, Minus, 0), "-3.14");
assert_eq!(to_string(f, -3.14, MinusPlus, 0), "-3.14");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 0), "-3.14");
assert_eq!(to_string(f, 3.14, Minus, 1), "3.14");
assert_eq!(to_string(f, 3.14, MinusRaw, 2), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, 3), "+3.140");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 4), "+3.1400");
assert_eq!(to_string(f, 3.14, Minus, 2), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, 4), "+3.1400");
assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusRaw, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusPlus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 8), "-3.14000000");
assert_eq!(to_string(f, 7.5e-11, Minus, 0), "0.000000000075");
assert_eq!(to_string(f, 7.5e-11, Minus, 3), "0.000000000075");
@ -615,68 +602,48 @@ where
let f = &mut f_;
assert_eq!(to_string(f, 0.0, Minus, (-4, 16), false), "0");
assert_eq!(to_string(f, 0.0, MinusRaw, (-4, 16), false), "0");
assert_eq!(to_string(f, 0.0, Minus, (-4, 16), false), "0");
assert_eq!(to_string(f, 0.0, MinusPlus, (-4, 16), false), "+0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, (-4, 16), false), "+0");
assert_eq!(to_string(f, -0.0, Minus, (-4, 16), false), "0");
assert_eq!(to_string(f, -0.0, MinusRaw, (-4, 16), false), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, (-4, 16), false), "+0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, (-4, 16), false), "-0");
assert_eq!(to_string(f, -0.0, Minus, (-4, 16), false), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, (-4, 16), false), "-0");
assert_eq!(to_string(f, 0.0, Minus, (0, 0), true), "0E0");
assert_eq!(to_string(f, 0.0, MinusRaw, (0, 0), false), "0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, (-9, -5), true), "+0E0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, (5, 9), false), "+0e0");
assert_eq!(to_string(f, -0.0, Minus, (0, 0), true), "0E0");
assert_eq!(to_string(f, -0.0, MinusRaw, (0, 0), false), "-0e0");
assert_eq!(to_string(f, -0.0, MinusPlus, (-9, -5), true), "+0E0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, (5, 9), false), "-0e0");
assert_eq!(to_string(f, 0.0, Minus, (0, 0), false), "0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, (5, 9), false), "+0e0");
assert_eq!(to_string(f, -0.0, Minus, (0, 0), true), "-0E0");
assert_eq!(to_string(f, -0.0, MinusPlus, (5, 9), false), "-0e0");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, (-4, 16), false), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusRaw, (-4, 16), true), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, (-4, 16), false), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlusRaw, (-4, 16), true), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, (-4, 16), true), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, (-4, 16), true), "+inf");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, (0, 0), false), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusRaw, (0, 0), true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, (-9, -5), false), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlusRaw, (5, 9), true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, (0, 0), true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, (5, 9), true), "NaN");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, (0, 0), false), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusRaw, (0, 0), true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, (-9, -5), false), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlusRaw, (5, 9), true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, (0, 0), true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, (5, 9), true), "-inf");
assert_eq!(to_string(f, 3.14, Minus, (-4, 16), false), "3.14");
assert_eq!(to_string(f, 3.14, MinusRaw, (-4, 16), false), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, (-4, 16), false), "+3.14");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, (-4, 16), false), "+3.14");
assert_eq!(to_string(f, -3.14, Minus, (-4, 16), false), "-3.14");
assert_eq!(to_string(f, -3.14, MinusRaw, (-4, 16), false), "-3.14");
assert_eq!(to_string(f, -3.14, MinusPlus, (-4, 16), false), "-3.14");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, (-4, 16), false), "-3.14");
assert_eq!(to_string(f, 3.14, Minus, (0, 0), true), "3.14E0");
assert_eq!(to_string(f, 3.14, MinusRaw, (0, 0), false), "3.14e0");
assert_eq!(to_string(f, 3.14, MinusPlus, (-9, -5), true), "+3.14E0");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, (5, 9), false), "+3.14e0");
assert_eq!(to_string(f, 3.14, Minus, (0, 0), false), "3.14e0");
assert_eq!(to_string(f, 3.14, MinusPlus, (5, 9), false), "+3.14e0");
assert_eq!(to_string(f, -3.14, Minus, (0, 0), true), "-3.14E0");
assert_eq!(to_string(f, -3.14, MinusRaw, (0, 0), false), "-3.14e0");
assert_eq!(to_string(f, -3.14, MinusPlus, (-9, -5), true), "-3.14E0");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, (5, 9), false), "-3.14e0");
assert_eq!(to_string(f, -3.14, Minus, (0, 0), false), "-3.14e0");
assert_eq!(to_string(f, -3.14, MinusPlus, (5, 9), false), "-3.14e0");
assert_eq!(to_string(f, 0.1, Minus, (-4, 16), false), "0.1");
assert_eq!(to_string(f, 0.1, MinusRaw, (-4, 16), false), "0.1");
assert_eq!(to_string(f, 0.1, Minus, (-4, 16), false), "0.1");
assert_eq!(to_string(f, 0.1, MinusPlus, (-4, 16), false), "+0.1");
assert_eq!(to_string(f, 0.1, MinusPlusRaw, (-4, 16), false), "+0.1");
assert_eq!(to_string(f, -0.1, Minus, (-4, 16), false), "-0.1");
assert_eq!(to_string(f, -0.1, MinusRaw, (-4, 16), false), "-0.1");
assert_eq!(to_string(f, -0.1, MinusPlus, (-4, 16), false), "-0.1");
assert_eq!(to_string(f, -0.1, MinusPlusRaw, (-4, 16), false), "-0.1");
assert_eq!(to_string(f, 0.1, Minus, (0, 0), true), "1E-1");
assert_eq!(to_string(f, 0.1, MinusRaw, (0, 0), false), "1e-1");
assert_eq!(to_string(f, 0.1, MinusPlus, (-9, -5), true), "+1E-1");
assert_eq!(to_string(f, 0.1, MinusPlusRaw, (5, 9), false), "+1e-1");
assert_eq!(to_string(f, 0.1, Minus, (0, 0), false), "1e-1");
assert_eq!(to_string(f, 0.1, MinusPlus, (5, 9), false), "+1e-1");
assert_eq!(to_string(f, -0.1, Minus, (0, 0), true), "-1E-1");
assert_eq!(to_string(f, -0.1, MinusRaw, (0, 0), false), "-1e-1");
assert_eq!(to_string(f, -0.1, MinusPlus, (-9, -5), true), "-1E-1");
assert_eq!(to_string(f, -0.1, MinusPlusRaw, (5, 9), false), "-1e-1");
assert_eq!(to_string(f, -0.1, Minus, (0, 0), false), "-1e-1");
assert_eq!(to_string(f, -0.1, MinusPlus, (5, 9), false), "-1e-1");
assert_eq!(to_string(f, 7.5e-11, Minus, (-4, 16), false), "7.5e-11");
assert_eq!(to_string(f, 7.5e-11, Minus, (-11, 10), false), "0.000000000075");
@ -734,68 +701,51 @@ where
let f = &mut f_;
assert_eq!(to_string(f, 0.0, Minus, 1, true), "0E0");
assert_eq!(to_string(f, 0.0, MinusRaw, 1, false), "0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, 1, true), "+0E0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 1, false), "+0e0");
assert_eq!(to_string(f, -0.0, Minus, 1, true), "0E0");
assert_eq!(to_string(f, -0.0, MinusRaw, 1, false), "-0e0");
assert_eq!(to_string(f, -0.0, MinusPlus, 1, true), "+0E0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 1, false), "-0e0");
assert_eq!(to_string(f, 0.0, Minus, 1, false), "0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, 1, false), "+0e0");
assert_eq!(to_string(f, -0.0, Minus, 1, true), "-0E0");
assert_eq!(to_string(f, -0.0, MinusPlus, 1, false), "-0e0");
assert_eq!(to_string(f, 0.0, Minus, 2, true), "0.0E0");
assert_eq!(to_string(f, 0.0, MinusRaw, 2, false), "0.0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, 2, true), "+0.0E0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 2, false), "+0.0e0");
assert_eq!(to_string(f, -0.0, Minus, 8, true), "0.0000000E0");
assert_eq!(to_string(f, -0.0, MinusRaw, 8, false), "-0.0000000e0");
assert_eq!(to_string(f, -0.0, MinusPlus, 8, true), "+0.0000000E0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 8, false), "-0.0000000e0");
assert_eq!(to_string(f, 0.0, Minus, 2, false), "0.0e0");
assert_eq!(to_string(f, 0.0, MinusPlus, 2, false), "+0.0e0");
assert_eq!(to_string(f, -0.0, Minus, 8, false), "-0.0000000e0");
assert_eq!(to_string(f, -0.0, MinusPlus, 8, false), "-0.0000000e0");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1, false), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusRaw, 1, true), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 1, false), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlusRaw, 1, true), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1, true), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 1, true), "+inf");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 8, false), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusRaw, 8, true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 8, false), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlusRaw, 8, true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 8, true), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 8, true), "NaN");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 64, false), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusRaw, 64, true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64, false), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlusRaw, 64, true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 64, true), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64, true), "-inf");
assert_eq!(to_string(f, 3.14, Minus, 1, true), "3E0");
assert_eq!(to_string(f, 3.14, MinusRaw, 1, false), "3e0");
assert_eq!(to_string(f, 3.14, MinusPlus, 1, true), "+3E0");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 1, false), "+3e0");
assert_eq!(to_string(f, 3.14, Minus, 1, false), "3e0");
assert_eq!(to_string(f, 3.14, MinusPlus, 1, false), "+3e0");
assert_eq!(to_string(f, -3.14, Minus, 2, true), "-3.1E0");
assert_eq!(to_string(f, -3.14, MinusRaw, 2, false), "-3.1e0");
assert_eq!(to_string(f, -3.14, MinusPlus, 2, true), "-3.1E0");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 2, false), "-3.1e0");
assert_eq!(to_string(f, -3.14, Minus, 2, false), "-3.1e0");
assert_eq!(to_string(f, -3.14, MinusPlus, 2, false), "-3.1e0");
assert_eq!(to_string(f, 3.14, Minus, 3, true), "3.14E0");
assert_eq!(to_string(f, 3.14, MinusRaw, 3, false), "3.14e0");
assert_eq!(to_string(f, 3.14, MinusPlus, 3, true), "+3.14E0");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 3, false), "+3.14e0");
assert_eq!(to_string(f, 3.14, Minus, 3, false), "3.14e0");
assert_eq!(to_string(f, 3.14, MinusPlus, 3, false), "+3.14e0");
assert_eq!(to_string(f, -3.14, Minus, 4, true), "-3.140E0");
assert_eq!(to_string(f, -3.14, MinusRaw, 4, false), "-3.140e0");
assert_eq!(to_string(f, -3.14, MinusPlus, 4, true), "-3.140E0");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 4, false), "-3.140e0");
assert_eq!(to_string(f, -3.14, Minus, 4, false), "-3.140e0");
assert_eq!(to_string(f, -3.14, MinusPlus, 4, false), "-3.140e0");
assert_eq!(to_string(f, 0.195, Minus, 1, false), "2e-1");
assert_eq!(to_string(f, 0.195, MinusRaw, 1, true), "2E-1");
assert_eq!(to_string(f, 0.195, MinusPlus, 1, false), "+2e-1");
assert_eq!(to_string(f, 0.195, MinusPlusRaw, 1, true), "+2E-1");
assert_eq!(to_string(f, 0.195, Minus, 1, true), "2E-1");
assert_eq!(to_string(f, 0.195, MinusPlus, 1, true), "+2E-1");
assert_eq!(to_string(f, -0.195, Minus, 2, false), "-2.0e-1");
assert_eq!(to_string(f, -0.195, MinusRaw, 2, true), "-2.0E-1");
assert_eq!(to_string(f, -0.195, MinusPlus, 2, false), "-2.0e-1");
assert_eq!(to_string(f, -0.195, MinusPlusRaw, 2, true), "-2.0E-1");
assert_eq!(to_string(f, -0.195, Minus, 2, true), "-2.0E-1");
assert_eq!(to_string(f, -0.195, MinusPlus, 2, true), "-2.0E-1");
assert_eq!(to_string(f, 0.195, Minus, 3, false), "1.95e-1");
assert_eq!(to_string(f, 0.195, MinusRaw, 3, true), "1.95E-1");
assert_eq!(to_string(f, 0.195, MinusPlus, 3, false), "+1.95e-1");
assert_eq!(to_string(f, 0.195, MinusPlusRaw, 3, true), "+1.95E-1");
assert_eq!(to_string(f, 0.195, Minus, 3, true), "1.95E-1");
assert_eq!(to_string(f, 0.195, MinusPlus, 3, true), "+1.95E-1");
assert_eq!(to_string(f, -0.195, Minus, 4, false), "-1.950e-1");
assert_eq!(to_string(f, -0.195, MinusRaw, 4, true), "-1.950E-1");
assert_eq!(to_string(f, -0.195, MinusPlus, 4, false), "-1.950e-1");
assert_eq!(to_string(f, -0.195, MinusPlusRaw, 4, true), "-1.950E-1");
assert_eq!(to_string(f, -0.195, Minus, 4, true), "-1.950E-1");
assert_eq!(to_string(f, -0.195, MinusPlus, 4, true), "-1.950E-1");
assert_eq!(to_string(f, 9.5, Minus, 1, false), "1e1");
assert_eq!(to_string(f, 9.5, Minus, 2, false), "9.5e0");
@ -1007,68 +957,48 @@ where
let f = &mut f_;
assert_eq!(to_string(f, 0.0, Minus, 0), "0");
assert_eq!(to_string(f, 0.0, MinusRaw, 0), "0");
assert_eq!(to_string(f, 0.0, MinusPlus, 0), "+0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 0), "+0");
assert_eq!(to_string(f, -0.0, Minus, 0), "0");
assert_eq!(to_string(f, -0.0, MinusRaw, 0), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, 0), "+0");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 0), "-0");
assert_eq!(to_string(f, -0.0, Minus, 0), "-0");
assert_eq!(to_string(f, -0.0, MinusPlus, 0), "-0");
assert_eq!(to_string(f, 0.0, Minus, 1), "0.0");
assert_eq!(to_string(f, 0.0, MinusRaw, 1), "0.0");
assert_eq!(to_string(f, 0.0, MinusPlus, 1), "+0.0");
assert_eq!(to_string(f, 0.0, MinusPlusRaw, 1), "+0.0");
assert_eq!(to_string(f, -0.0, Minus, 8), "0.00000000");
assert_eq!(to_string(f, -0.0, MinusRaw, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlus, 8), "+0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlusRaw, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, Minus, 8), "-0.00000000");
assert_eq!(to_string(f, -0.0, MinusPlus, 8), "-0.00000000");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 0), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusRaw, 1), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 8), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlusRaw, 64), "+inf");
assert_eq!(to_string(f, 1.0 / 0.0, Minus, 1), "inf");
assert_eq!(to_string(f, 1.0 / 0.0, MinusPlus, 64), "+inf");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 0), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusRaw, 1), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 8), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlusRaw, 64), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, Minus, 1), "NaN");
assert_eq!(to_string(f, 0.0 / 0.0, MinusPlus, 64), "NaN");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 0), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusRaw, 1), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 8), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlusRaw, 64), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, Minus, 1), "-inf");
assert_eq!(to_string(f, -1.0 / 0.0, MinusPlus, 64), "-inf");
assert_eq!(to_string(f, 3.14, Minus, 0), "3");
assert_eq!(to_string(f, 3.14, MinusRaw, 0), "3");
assert_eq!(to_string(f, 3.14, Minus, 0), "3");
assert_eq!(to_string(f, 3.14, MinusPlus, 0), "+3");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 0), "+3");
assert_eq!(to_string(f, -3.14, Minus, 0), "-3");
assert_eq!(to_string(f, -3.14, MinusRaw, 0), "-3");
assert_eq!(to_string(f, -3.14, Minus, 0), "-3");
assert_eq!(to_string(f, -3.14, MinusPlus, 0), "-3");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 0), "-3");
assert_eq!(to_string(f, 3.14, Minus, 1), "3.1");
assert_eq!(to_string(f, 3.14, MinusRaw, 2), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, 3), "+3.140");
assert_eq!(to_string(f, 3.14, MinusPlusRaw, 4), "+3.1400");
assert_eq!(to_string(f, 3.14, Minus, 2), "3.14");
assert_eq!(to_string(f, 3.14, MinusPlus, 4), "+3.1400");
assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, Minus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusRaw, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusPlus, 8), "-3.14000000");
assert_eq!(to_string(f, -3.14, MinusPlusRaw, 8), "-3.14000000");
assert_eq!(to_string(f, 0.195, Minus, 0), "0");
assert_eq!(to_string(f, 0.195, MinusRaw, 0), "0");
assert_eq!(to_string(f, 0.195, MinusPlus, 0), "+0");
assert_eq!(to_string(f, 0.195, MinusPlusRaw, 0), "+0");
assert_eq!(to_string(f, -0.195, Minus, 0), "-0");
assert_eq!(to_string(f, -0.195, MinusRaw, 0), "-0");
assert_eq!(to_string(f, -0.195, Minus, 0), "-0");
assert_eq!(to_string(f, -0.195, MinusPlus, 0), "-0");
assert_eq!(to_string(f, -0.195, MinusPlusRaw, 0), "-0");
assert_eq!(to_string(f, 0.195, Minus, 1), "0.2");
assert_eq!(to_string(f, 0.195, MinusRaw, 2), "0.20");
assert_eq!(to_string(f, 0.195, MinusPlus, 3), "+0.195");
assert_eq!(to_string(f, 0.195, MinusPlusRaw, 4), "+0.1950");
assert_eq!(to_string(f, 0.195, Minus, 2), "0.20");
assert_eq!(to_string(f, 0.195, MinusPlus, 4), "+0.1950");
assert_eq!(to_string(f, -0.195, Minus, 5), "-0.19500");
assert_eq!(to_string(f, -0.195, MinusRaw, 6), "-0.195000");
assert_eq!(to_string(f, -0.195, MinusPlus, 7), "-0.1950000");
assert_eq!(to_string(f, -0.195, MinusPlusRaw, 8), "-0.19500000");
assert_eq!(to_string(f, -0.195, Minus, 6), "-0.195000");
assert_eq!(to_string(f, -0.195, MinusPlus, 8), "-0.19500000");
assert_eq!(to_string(f, 999.5, Minus, 0), "1000");
assert_eq!(to_string(f, 999.5, Minus, 1), "999.5");

View File

@ -0,0 +1,158 @@
//! IEEE 754 floating point compliance tests
//!
//! To understand IEEE 754's requirements on a programming language, one must understand that the
//! requirements of IEEE 754 rest on the total programming environment, and not entirely on any
//! one component. That means the hardware, language, and even libraries are considered part of
//! conforming floating point support in a programming environment.
//!
//! A programming language's duty, accordingly, is:
//! 1. offer access to the hardware where the hardware offers support
//! 2. provide operations that fulfill the remaining requirements of the standard
//! 3. provide the ability to write additional software that can fulfill those requirements
//!
//! This may be fulfilled in any combination that the language sees fit. However, to claim that
//! a language supports IEEE 754 is to suggest that it has fulfilled requirements 1 and 2, without
//! deferring minimum requirements to libraries. This is because support for IEEE 754 is defined
//! as complete support for at least one specified floating point type as an "arithmetic" and
//! "interchange" format, plus specified type conversions to "external character sequences" and
//! integer types.
//!
//! For our purposes,
//! "interchange format" => f32, f64
//! "arithmetic format" => f32, f64, and any "soft floats"
//! "external character sequence" => str from any float
//! "integer format" => {i,u}{8,16,32,64,128}
//!
//! None of these tests are against Rust's own implementation. They are only tests against the
//! standard. That is why they accept wildly diverse inputs or may seem to duplicate other tests.
//! Please consider this carefully when adding, removing, or reorganizing these tests. They are
//! here so that it is clear what tests are required by the standard and what can be changed.
use ::core::str::FromStr;
// IEEE 754 for many tests is applied to specific bit patterns.
// These generally are not applicable to NaN, however.
macro_rules! assert_biteq {
($lhs:expr, $rhs:expr) => {
assert_eq!($lhs.to_bits(), $rhs.to_bits())
};
}
// ToString uses the default fmt::Display impl without special concerns, and bypasses other parts
// of the formatting infrastructure, which makes it ideal for testing here.
#[allow(unused_macros)]
macro_rules! roundtrip {
($f:expr => $t:ty) => {
($f).to_string().parse::<$t>().unwrap()
};
}
macro_rules! assert_floats_roundtrip {
($f:ident) => {
assert_biteq!(f32::$f, roundtrip!(f32::$f => f32));
assert_biteq!(f64::$f, roundtrip!(f64::$f => f64));
};
($f:expr) => {
assert_biteq!($f as f32, roundtrip!($f => f32));
assert_biteq!($f as f64, roundtrip!($f => f64));
}
}
macro_rules! assert_floats_bitne {
($lhs:ident, $rhs:ident) => {
assert_ne!(f32::$lhs.to_bits(), f32::$rhs.to_bits());
assert_ne!(f64::$lhs.to_bits(), f64::$rhs.to_bits());
};
($lhs:expr, $rhs:expr) => {
assert_ne!(f32::to_bits($lhs), f32::to_bits($rhs));
assert_ne!(f64::to_bits($lhs), f64::to_bits($rhs));
};
}
// We must preserve signs on all numbers. That includes zero.
// -0 and 0 are == normally, so test bit equality.
#[test]
fn preserve_signed_zero() {
assert_floats_roundtrip!(-0.0);
assert_floats_roundtrip!(0.0);
assert_floats_bitne!(0.0, -0.0);
}
#[test]
fn preserve_signed_infinity() {
assert_floats_roundtrip!(INFINITY);
assert_floats_roundtrip!(NEG_INFINITY);
assert_floats_bitne!(INFINITY, NEG_INFINITY);
}
#[test]
fn infinity_to_str() {
assert!(match f32::INFINITY.to_string().to_lowercase().as_str() {
"+infinity" | "infinity" => true,
"+inf" | "inf" => true,
_ => false,
});
assert!(
match f64::INFINITY.to_string().to_lowercase().as_str() {
"+infinity" | "infinity" => true,
"+inf" | "inf" => true,
_ => false,
},
"Infinity must write to a string as some casing of inf or infinity, with an optional +."
);
}
#[test]
fn neg_infinity_to_str() {
assert!(match f32::NEG_INFINITY.to_string().to_lowercase().as_str() {
"-infinity" | "-inf" => true,
_ => false,
});
assert!(
match f64::NEG_INFINITY.to_string().to_lowercase().as_str() {
"-infinity" | "-inf" => true,
_ => false,
},
"Negative Infinity must write to a string as some casing of -inf or -infinity"
)
}
#[test]
fn nan_to_str() {
assert!(
match f32::NAN.to_string().to_lowercase().as_str() {
"nan" | "+nan" | "-nan" => true,
_ => false,
},
"NaNs must write to a string as some casing of nan."
)
}
// "+"?("inf"|"infinity") in any case => Infinity
#[test]
fn infinity_from_str() {
assert_biteq!(f32::INFINITY, f32::from_str("infinity").unwrap());
assert_biteq!(f32::INFINITY, f32::from_str("inf").unwrap());
assert_biteq!(f32::INFINITY, f32::from_str("+infinity").unwrap());
assert_biteq!(f32::INFINITY, f32::from_str("+inf").unwrap());
// yes! this means you are weLcOmE tO mY iNfInItElY tWiStEd MiNd
assert_biteq!(f32::INFINITY, f32::from_str("+iNfInItY").unwrap());
}
// "-inf"|"-infinity" in any case => Negative Infinity
#[test]
fn neg_infinity_from_str() {
assert_biteq!(f32::NEG_INFINITY, f32::from_str("-infinity").unwrap());
assert_biteq!(f32::NEG_INFINITY, f32::from_str("-inf").unwrap());
assert_biteq!(f32::NEG_INFINITY, f32::from_str("-INF").unwrap());
assert_biteq!(f32::NEG_INFINITY, f32::from_str("-INFinity").unwrap());
}
// ("+"|"-"")?"s"?"nan" in any case => qNaN
#[test]
fn qnan_from_str() {
assert!("nan".parse::<f32>().unwrap().is_nan());
assert!("-nan".parse::<f32>().unwrap().is_nan());
assert!("+nan".parse::<f32>().unwrap().is_nan());
assert!("+NAN".parse::<f32>().unwrap().is_nan());
assert!("-NaN".parse::<f32>().unwrap().is_nan());
}

View File

@ -32,6 +32,7 @@ mod flt2dec;
mod ops;
mod wrapping;
mod ieee754;
mod nan;
/// Adds the attribute to all items in the block.

View File

@ -805,10 +805,12 @@ mod prim_tuple {}
/// often discard insignificant digits: `println!("{}", 1.0f32 / 5.0f32)` will
/// print `0.2`.
///
/// Additionally, `f32` can represent a couple of special values:
/// Additionally, `f32` can represent some special values:
///
/// - `-0`: this is just due to how floats are encoded. It is semantically
/// equivalent to `0` and `-0.0 == 0.0` results in `true`.
/// - -0.0: IEEE 754 floating point numbers have a bit that indicates their sign, so -0.0 is a
/// possible value. For comparison `-0.0 == +0.0` is true but floating point operations can
/// carry the sign bit through arithmetic operations. This means `-1.0 * 0.0` produces -0.0 and
/// a negative number rounded to a value smaller than a float can represent also produces -0.0.
/// - [∞](#associatedconstant.INFINITY) and
/// [−∞](#associatedconstant.NEG_INFINITY): these result from calculations
/// like `1.0 / 0.0`.

View File

@ -51,7 +51,7 @@
}
bb2: {
discriminant(_6) = 2; // scope 1 at $DIR/funky_arms.rs:21:17: 21:41
discriminant(_6) = 1; // scope 1 at $DIR/funky_arms.rs:21:17: 21:41
goto -> bb4; // scope 1 at $DIR/funky_arms.rs:19:16: 22:6
}