-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use constant for 180/π in to_degrees #47919
Conversation
r? @shepmaster (rust_highfive has picked a reviewer for you, use r? to override) |
r? @rkruppe you seemed in favor of this. |
src/libcore/num/f32.rs
Outdated
@@ -239,7 +239,9 @@ impl Float for f32 { | |||
/// Converts to degrees, assuming the number is in radians. | |||
#[inline] | |||
fn to_degrees(self) -> f32 { | |||
self * (180.0f32 / consts::PI) | |||
// Use a constant for better precision. | |||
let pis_in_180 = 57.2957795130823208767981548141051703_f32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not an actual const
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that makes more sense stylistically.
src/libcore/num/f64.rs
Outdated
@@ -237,7 +237,9 @@ impl Float for f64 { | |||
/// Converts to degrees, assuming the number is in radians. | |||
#[inline] | |||
fn to_degrees(self) -> f64 { | |||
self * (180.0f64 / consts::PI) | |||
// Use a constant for better precision. | |||
let pis_in_180 = 57.2957795130823208767981548141051703_f64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there be more digits here than in f32
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both literals actually have more digits than necessary: they both have 36 significant figures, which was chosen to be consistent with the existing definitions of mathematical constants (for both f32
and f64
).
Curiously, f64::to_degrees already passes the test without this change and |
I think in this case, double rounding should not be an issue, because the precision to which the number is rounded in decimal form is significantly higher than the floating-point precision. I imagine the change isn't actually necessary for |
Double rounding can be an issue regardless of what intermediate precision you round to. Suppose the full binary expansion of a number looks like this:
If we first round (half-to-even) to position |
If we truncate/round to |
I'm not quite sure if I understand correctly, but if you produce anything other than But this is a theroetical discussion. The actual interesting part is whether the f64 produced from the decimal literal in this code is the correct rounding of the actual value of 180/pi. I'm rather busy now so I don't know when I'll have the time to verify that. |
I guess my real question is that, this code compiles: fn main() {
let pis_in_180_below = 57.29577951308232087679815_f64;
let pis_in_180_above = 57.29577951308232087679816_f64;
// pis_in_180_below < pis_in_180 < pis_in_180_above (in terms of their literals)
assert_eq!(pis_in_180_below, pis_in_180_above);
} so assuming the floating-point literal parser is well-behaved, then any literal value (including a hypothetically-infinite sequence) between these two values is equal to the constant in this code. This assumes that the parser always produces the closest floating-point number to the literal provided (which seems like a reasonable assumption, given that Rust even allows literals with more precision than |
In both previous and new code, the division is evaluated during the construction of LLVM-IR and obtains following representations (the hexidecimal form used is whatever the IEEE standard specifies):
So at least for f64, the result is equivalent. |
@nagisa Thanks! But I believe that just confirms what was observed earlier in this thread? The tricky question is whether you'd also get @varkor's last comment, however, settles the question with less effort than what I had in mind. I'm convinced now the dedicated constant is both correct and unnecessary for f64. |
src/libcore/num/f64.rs
Outdated
@@ -237,7 +237,9 @@ impl Float for f64 { | |||
/// Converts to degrees, assuming the number is in radians. | |||
#[inline] | |||
fn to_degrees(self) -> f64 { | |||
self * (180.0f64 / consts::PI) | |||
// Use a constant for better precision. | |||
const PIS_IN_180: f64 = 57.2957795130823208767981548141051703_f64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mentioned consistency as reason to have the constant here as well but I'd rather not have an opaque magic number duplicated for no functional reason. I think it's better to leave the code in place and add a comment saying that 180.0f64 / consts::PI
is correctly rounded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and probably call out that this differs from f32::to_degrees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, sure 👍
d07c26e
to
febba80
Compare
Great, thanks! r=me after squashing |
Would it make sense to compute the f64 constant and convert it to f32 in the f32 impl? |
The current `f32|f64.to_degrees` implementation uses a division to calculate 180/π, which causes a loss of precision. Using a constant is still not perfect (implementing a maximally-precise algorithm would come with a high performance cost), but improves precision with a minimal change.
febba80
to
e34c31b
Compare
@ranma42 It's possible that this doesn't give the right result (because it's double rounding differently than the ways examined in previously in this discussion, which turned out to be fine) and it doesn't seem easier to verify that it gives the right result than to verify that the constant is correct. |
@bors r+ |
📌 Commit e34c31b has been approved by |
@bors rollup |
…ppe Use constant for 180/π in to_degrees The current `f32|f64.to_degrees` implementation uses a division to calculate `180/π`, which causes a loss of precision. Using a constant is still not perfect (implementing a maximally-precise algorithm would come with a high performance cost), but improves precision with a minimal change. As per the discussion in rust-lang#29944, this fixes rust-lang#29944 (the costs of improving the precision further would not outweigh the gains).
rust-lang#47919 regressed some test cases for `f32::to_degrees`, while improving others. This change satisfies all previous test cases and should be more accurate than both previous implementations. The `f32` to `f64` cast is lossless, `f64::to_degrees` should provide more accuracy than a native `f32::to_degrees`, and conversion back to `f32` will be as accurate a conversion as possible. It can be hard to reason about floating-point accuracy, but given that this passes all the tests, I feel confident it's an improvement.
The current
f32|f64.to_degrees
implementation uses a division to calculate180/π
, which causes a loss of precision. Using a constant is still not perfect (implementing a maximally-precise algorithm would come with a high performance cost), but improves precision with a minimal change.As per the discussion in #29944, this fixes #29944 (the costs of improving the precision further would not outweigh the gains).