Skip to content

f32 division/multiplication and .tan() platform behavior  #75786

Closed

Description

@Blisto91

I know floats aren't exactly known for their precision and i might be missing something.
But i'm seeing some odd platform dependent behavior when i combine a f32 float division with .tan()

This code run on mac, linux and windows

let floaty: f32 = 1.22222; // 5 decimal points
println!("{:.32}", floaty);
println!("{:.32}", floaty / 2.0);
println!("{:.32}", (floaty / 2.0).tan());

Gives the following printout

// mac
1.22221994400024414062500000000000
0.61110997200012207031250000000000
0.70057231187820434570312500000000

// linux
1.22221994400024414062500000000000
0.61110997200012207031250000000000
0.70057231187820434570312500000000

// windows
1.22221994400024414062500000000000
0.61110997200012207031250000000000
0.70057231187820434570312500000000

All the outputs are the same as i guess is expected.

Now i add one more decimal

let floaty: f32 = 1.222222; // 6 decimal points
println!("{:.32}", floaty);
println!("{:.32}", floaty / 2.0);
println!("{:.32}", (floaty / 2.0).tan());

Which outputs

// mac & linux 
1.22222197055816650390625000000000
0.61111098527908325195312500000000
0.70057380199432373046875000000000

// windows
1.22222197055816650390625000000000
0.61111098527908325195312500000000
0.70057386159896850585937500000000

Now windows suddenly shows a different output on the last line.

I have testet this on my windows 10 pc, linux laptop (mint) and a github actions matrix of ubuntu, windows, mac, nightly, beta and stable.

Is this a bug or am i missing something?

Activity

  1. cuviper commented on Aug 21, 2020

    @cuviper
    Member

    This is similar to #73420 -- your Windows result looks like it has computed via f64.

    I tried this on the playground (Linux):

    fn main() {
        let floaty: f32 = 1.222222; // 6 decimal points
        println!("{:.32}", floaty);
        println!("{:.32}", floaty / 2.0);
        println!("{:.32}", (floaty / 2.0).tan());
        println!("{:.32}", ((floaty / 2.0) as f64).tan() as f32);
    }
    1.22222197055816650390625000000000
    0.61111098527908325195312500000000
    0.70057380199432373046875000000000
    0.70057386159896850585937500000000
    
  2. Blisto91 commented on Aug 21, 2020

    @Blisto91
    Author

    Ah yea i see what you mean.
    I was also messing around with f64 to see if i could make it act weird, but was without luck.
    Didn't think to compare the values.

  3. Blisto91 commented on Aug 21, 2020

    @Blisto91
    Author

    Adding some more stuff i just found.
    With multiplication ubuntu is the odd one out.

    let floaty: f32 = 1.22222;  // 5 decimal points
    println!("{:.32}", floaty);
    println!("{:.32}", floaty * 2.0);
    println!("{:.32}", (floaty * 2.0).tan()); 
    // mac linux windows
    1.22221994400024414062500000000000
    2.44443988800048828125000000000000
    -0.83743280172348022460937500000000
    
    let floaty: f32 = 1.222222;  // 6 decimal points
    println!("{:.32}", floaty);
    println!("{:.32}", floaty * 2.0);
    println!("{:.32}", (floaty * 2.0).tan()); 
    // mac windows
    1.22222197055816650390625000000000
    2.44444394111633300781250000000000
    -0.83742588758468627929687500000000
    
    // linux
    1.22222197055816650390625000000000
    2.44444394111633300781250000000000
    -0.83742594718933105468750000000000
    
  4. changed the title [-]f32 division and .tan() platform behavior [/-] [+]f32 division/multiplication and .tan() platform behavior [/+] on Aug 21, 2020
  5. scottmcm commented on Aug 24, 2020

    @scottmcm
    Member

    This is probably just because of the tan -- multiplication and division are required to be good to ½ULP, but IIRC the trig functions only need to be good to 1ULP, which means it's expected that there can be differences.

    If you throw that last example into float toy, you'll see

    0xBF56618B -0.8374259
    0xBF56618C -0.83742594
             ^
    

    A difference of 1ULP, as expected.

    There are plenty of resources about floating point determinism related to real-time strategy games, see for example the things referenced in https://gamedev.stackexchange.com/a/174374

    So I think there isn't actually anything to fix here, as unfortunate as that is. This is just why games tend to use their own trig approximations for anything that needs to be portable, rather than the platform ones.

  6. Blisto91 commented on Aug 24, 2020

    @Blisto91
    Author

    @scottmcm Thanks for the explanation! And thank you for the links.
    Gonna bookmark them. :)

    I was told that dimforge rapier is gonna have cross platform determinism so i was wondering.
    But as you said they probably have their own implementations. 👍

    If you say this is expected behavior then feel free to close it. I don't have enough knowledge on the subject to be able to chime in.

    Edit:
    Also gonna read up on posits because they sound interesting.

  7. RalfJung commented on Aug 30, 2020

    @RalfJung
    Member

    Indeed, multiplication and division should be consistent across platforms. Can you confirm that this is the case here? If yes, we should remove their mention from the issue title.

    tan, on the other hand, doesn't have a precise spec -- so basically, this is expected behavior. I guess if we want to improve the situation here, we'd want to have a pure-Rust fast and precise implementation of tan (and friends) and make rustc use that instead of the system library. :D

  8. Blisto91 commented on Oct 1, 2020

    @Blisto91
    Author

    Sorry for not keeping this up to date. Sleep has not been agreeing with me lately.

    Will try and update this without the division/multiplication here soon when i have time

  9. Blisto91 commented on Jul 20, 2021

    @Blisto91
    Author

    Closing this for now as it doesn't focus on the actual problem without division/multiplication.
    Might open a new issue if I ever have the time and motivation.

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

    A-floating-pointArea: Floating point numbers and arithmetic

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions