From 698db13cd068b1807780f97344bb764efc87703f Mon Sep 17 00:00:00 2001 From: Karol Zwolak Date: Wed, 20 Aug 2025 20:23:08 +0200 Subject: [PATCH] improve float to_degrees/to_radians rounding comments and impl * revise comments explaining why we are using literal or expression * add unspecified precision comments as we don't guarantee precision * use expression in `f128::to_degrees()` * make `f64::to_degrees()` impl consistent with other functions --- library/core/src/num/f128.rs | 23 ++++++++++++++++++++--- library/core/src/num/f16.rs | 20 ++++++++++++++++++-- library/core/src/num/f32.rs | 20 +++++++++++++++++++- library/core/src/num/f64.rs | 26 ++++++++++++++++++++++---- 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 69e6c100e763a..4282b1af9f206 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -630,6 +630,13 @@ impl f128 { /// Converts radians to degrees. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// #![feature(f128)] /// # // FIXME(f16_f128): remove when `eqtf2` is available @@ -645,13 +652,22 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_degrees(self) -> Self { - // Use a literal for better precision. - const PIS_IN_180: f128 = 57.2957795130823208767981548141051703324054724665643215491602_f128; + // The division here is correctly rounded with respect to the true value of 180/π. + // Although π is irrational and already rounded, the double rounding happens + // to produce correct result for f128. + const PIS_IN_180: f128 = 180.0 / consts::PI; self * PIS_IN_180 } /// Converts degrees to radians. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// #![feature(f128)] /// # // FIXME(f16_f128): remove when `eqtf2` is available @@ -668,7 +684,8 @@ impl f128 { #[unstable(feature = "f128", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_radians(self) -> f128 { - // Use a literal for better precision. + // Use a literal to avoid double rounding, consts::PI is already rounded, + // and dividing would round again. const RADS_PER_DEG: f128 = 0.0174532925199432957692369076848861271344287188854172545609719_f128; self * RADS_PER_DEG diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index b66cef03d2003..23a8661c14b7a 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -625,6 +625,13 @@ impl f16 { /// Converts radians to degrees. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// #![feature(f16)] /// # // FIXME(f16_f128): extendhfsf2, truncsfhf2, __gnu_h2f_ieee, __gnu_f2h_ieee missing for many platforms @@ -640,13 +647,21 @@ impl f16 { #[unstable(feature = "f16", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_degrees(self) -> Self { - // Use a literal for better precision. + // Use a literal to avoid double rounding, consts::PI is already rounded, + // and dividing would round again. const PIS_IN_180: f16 = 57.2957795130823208767981548141051703_f16; self * PIS_IN_180 } /// Converts degrees to radians. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// #![feature(f16)] /// # // FIXME(f16_f128): extendhfsf2, truncsfhf2, __gnu_h2f_ieee, __gnu_f2h_ieee missing for many platforms @@ -663,7 +678,8 @@ impl f16 { #[unstable(feature = "f16", issue = "116909")] #[must_use = "this returns the result of the operation, without modifying the original"] pub const fn to_radians(self) -> f16 { - // Use a literal for better precision. + // Use a literal to avoid double rounding, consts::PI is already rounded, + // and dividing would round again. const RADS_PER_DEG: f16 = 0.017453292519943295769236907684886_f16; self * RADS_PER_DEG } diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index f8344da79ad40..da08bfc595059 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -839,6 +839,13 @@ impl f32 { /// Converts radians to degrees. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// let angle = std::f32::consts::PI; /// @@ -852,13 +859,21 @@ impl f32 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn to_degrees(self) -> f32 { - // Use a constant for better precision. + // Use a literal to avoid double rounding, consts::PI is already rounded, + // and dividing would round again. const PIS_IN_180: f32 = 57.2957795130823208767981548141051703_f32; self * PIS_IN_180 } /// Converts degrees to radians. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// let angle = 180.0f32; /// @@ -872,6 +887,9 @@ impl f32 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn to_radians(self) -> f32 { + // The division here is correctly rounded with respect to the true value of π/180. + // Although π is irrational and already rounded, the double rounding happens + // to produce correct result for f32. const RADS_PER_DEG: f32 = consts::PI / 180.0; self * RADS_PER_DEG } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 93da63c896e23..dc0977cb1474c 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -856,6 +856,13 @@ impl f64 { /// Converts radians to degrees. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// let angle = std::f64::consts::PI; /// @@ -869,14 +876,22 @@ impl f64 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn to_degrees(self) -> f64 { - // The division here is correctly rounded with respect to the true - // value of 180/π. (This differs from f32, where a constant must be - // used to ensure a correctly rounded result.) - self * (180.0f64 / consts::PI) + // The division here is correctly rounded with respect to the true value of 180/π. + // Although π is irrational and already rounded, the double rounding happens + // to produce correct result for f64. + const PIS_IN_180: f64 = 180.0 / consts::PI; + self * PIS_IN_180 } /// Converts degrees to radians. /// + /// # Unspecified precision + /// + /// The precision of this function is non-deterministic. This means it varies by platform, + /// Rust version, and can even differ within the same execution from one invocation to the next. + /// + /// # Examples + /// /// ``` /// let angle = 180.0_f64; /// @@ -890,6 +905,9 @@ impl f64 { #[rustc_const_stable(feature = "const_float_methods", since = "1.85.0")] #[inline] pub const fn to_radians(self) -> f64 { + // The division here is correctly rounded with respect to the true value of π/180. + // Although π is irrational and already rounded, the double rounding happens + // to produce correct result for f64. const RADS_PER_DEG: f64 = consts::PI / 180.0; self * RADS_PER_DEG }