Skip to content

f32::FRAC_PI_6.to_degrees() is now less accurate #48617

@cuviper

Description

@cuviper
Member

On stable, f32 FRAC_PI_6.to_degrees() returns exactly 30.0. On beta and nightly, it returns 30.000002.

This is probably related to #47919, which intended to increase accuracy. cc @varkor

Here's a full test of PI constants, asserting errors < 1e-6:

use std::f32::consts::*;

const DEG_RAD_PAIRS: [(f32, f32); 7] = [
    (0.0, 0.),
    (22.5, FRAC_PI_8),
    (30.0, FRAC_PI_6),
    (45.0, FRAC_PI_4),
    (60.0, FRAC_PI_3),
    (90.0, FRAC_PI_2),
    (180.0, PI),
];

fn main() {
    for &(deg, rad) in &DEG_RAD_PAIRS {
        let rad2 = deg.to_radians();
        let deg2 = rad.to_degrees();
        
        println!("degrees: {:?} {:?}", deg, deg2);
        assert!((deg2 - deg).abs() < 1e-6);
        
        println!("radians: {:?} {:?}", rad, rad2);
        assert!((rad2 - rad).abs() < 1e-6);
    }
}

playground

It passes on stable 1.24, but fails on beta and nightly.

Activity

varkor

varkor commented on Feb 28, 2018

@varkor
Member

Ah, this isn't on 😕
It seems like the improvements were not uniform. I'll get this fixed.

cuviper

cuviper commented on Feb 28, 2018

@cuviper
MemberAuthor

FWIW, I think that 1.0f32 test case, which spurred the change, is so trivial that it's misleading. This is a multiplicative identity, of course, but it's also not representative -- who ever uses exactly 1 radian? Accuracy with the PI constants will be more generally beneficial, even if it sacrifices the unit case. But hey, maybe there's a way to do better at both!

varkor

varkor commented on Feb 28, 2018

@varkor
Member

@cuviper: I agree — the identities with π are more important than the 1 radian case. The intention was to provide increased accuracy everywhere, and that was just one demonstrative test case, but clearly that hasn't worked out. I think I have an implementation that works for both these cases, though, so hopefully we can get the best of both worlds! (Floating point is always tricky!)

modified the milestones: 1.25, 1.26 on Feb 28, 2018
Mark-Simulacrum

Mark-Simulacrum commented on Mar 4, 2018

@Mark-Simulacrum
Member

We should probably revert the initial patch so as to not regress behavior here until we come to a more principled solution. Could someone prepare that PR for beta?

varkor

varkor commented on Mar 4, 2018

@varkor
Member

@Mark-Simulacrum: see the discussion in #48622. I think this this is an intended effect (the accuracy has regressed for some inputs, but overall the accuracy across the range of floating points has increased).

I'll write up a more detailed response later.

varkor

varkor commented on Mar 5, 2018

@varkor
Member

My inclination is to close this as expected breakage. I don't think we really explicitly acknowledged the cases where the double-rounding of the previous implementation of f32::to_degrees caused some inputs to be assigned correct outputs, but where the single-rounding of the new implementation caused the input to be off by 1 ULP. It's also unfortunate that some of the inputs for which the function has regressed are on "well-known" mathematical identities.

That said, as #48622 (comment) describes, the overall accuracy for f32::to_degrees has increased as expected: the number of inputs for which the output is off by 1 ULP is roughly 4.5x lower than the previous implementation. I think behaviour over the entire range is more important than particular identities (though if the proportion was close to 1, I would be disposed to keeping the implementation preserving them).

There are ways to fix this completely (e.g. an arbitrary-precision algorithm, or possibly even just using f64, as was the approach in #48622), but they incur costs that are not desirable for the typical user, who just wants a fast radians-to-degrees conversion.

Using to_degrees as if it were correct to 0.5 ULP on all inputs previously was incorrect, and that assumption has not changed (although I would treat an error of more than 1 ULP a bug) — in particular, assuming any particular equality holds exactly is fallacious. (Tangentially, I can't even think where one might even need a maximally accurate to_degrees function, but that's by-the-by.)

Therefore, though I concede it's a little disappointing that those particular cases have regressed, I think the current implementation is the preferred one.

(@rkruppe has proved more prudent on this issue previously, though, so I'd like to hear what they think before any final decision is made.)

hanna-kruppe

hanna-kruppe commented on Mar 5, 2018

@hanna-kruppe
Contributor

I generally agree that improvements "across the board" are better than spot fixes on identities. I do have some questions about the data in #48622 (comment) which I now posted there (sorry, I meant to post these earlier but I've been busy).

Regardless what we decide here, I think this isn't a drastic enough regression to justify any quick reverts, since as @varkor said:

Using to_degrees as if it were correct to 0.5 ULP on all inputs previously was incorrect, and that assumption has not changed (although I would treat an error of more than 1 ULP a bug) — in particular, assuming any particular equality holds exactly is fallacious. (Tangentially, I can't even think where one might even need a maximally accurate to_degrees function, but that's by-the-by.)

If we decide to revert, we can do so after careful deliberation. This slipping in a stable release, even if we ultimately revert it, doesn't seem very bad.

18 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @cuviper@alexcrichton@nikomatsakis@hanna-kruppe@varkor

        Issue actions

          f32::FRAC_PI_6.to_degrees() is now less accurate · Issue #48617 · rust-lang/rust