skip to Main Content

The following JavaScript function consistently works as expected:

function getvalue(a, b, c) {
    Math.floor((a / b) % c)
}

When I write this in Rust and use it within JS via wasm_bindgen, every once in a while and seemingly randomly I get a result which is different than the value returned from the function above:

pub fn get_value(a: f32, b: f32, c: f32) -> i32 {
    ((a / b) % c).floor() as i32
}

Some examples:

a = 33339077
b = 53.32715671989507
c = 3.5454545454545454

// JS -> 3, Rust -> 2

a = 33340860
b = 53.32715671989507
c = 3.5454545454545454

// JS -> 0, Rust -> 1

What am I missing here, and what can be done to make the Rust function return the same value as the JS one?

3

Answers


  1. Well, your JS function

    function getvalue() {
        Math.floor((a / b) % c)
    }
    

    Doesn’t define a, b, or c. And the expression (a/b) % c yields undefined.

    But it doesn’t matter, because getvalue() doesn’t return a value — it returns, by default, undefined.

    Your Rust function

    pub fn get_value(a: f32, b: f32, c: f32) -> i32 {
        ((a / b) % c).floor() as i32
    }
    

    Assuming that f32 is a single precision IEEE float and i32 is a 32-bit int,
    .
    .
    .
    Well, it doesn’t return a value either.

    But more (and most?) importantly, all numeric values in JS are IEEE double precision floats.

    See Goldberg’s paper, "What Every Computer Scientist Should Know About Floating-Point Arithmetic"

    https://pages.cs.wisc.edu/~david/courses/cs552/S12/handouts/goldberg-floating-point.pdf

    https://www.itu.dk/~sestoft/bachelor/IEEE754_article.pdf

    https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

    Also see What Every Programmer Should Know About Floating-Point Arithmetic,or, Why don’t my numbers add up?

    Login or Signup to reply.
  2. JavaScript uses double-precision floating-point numbers, but your Rust function uses single-precision. If you switch to f64 you will get the same result:

    pub fn get_value32 (a: f32, b: f32, c: f32) -> i32 {
        ((a / b) % c).floor() as i32
    }
    
    pub fn get_value64 (a: f64, b: f64, c: f64) -> i32 {
        ((a / b) % c).floor() as i32
    }
    
    fn main() {
        println!("f32: {}", get_value32 (33339077., 53.32715671989507, 3.5454545454545454));
        println!("f64: {}", get_value64 (33339077., 53.32715671989507, 3.5454545454545454));
    }
    

    gives:

    f32: 2
    f64: 3
    

    Playground

    Login or Signup to reply.
  3. The maximum safe integer for a f32 is 16777215. Integers above this value can not be stored exactly. See this rust code:

    fn main() {
        let a:f32 = 33339077.0f32;
        println!("{a:?}");
        // Output: 33339076.0
    }
    

    The nearest value to 33339077 that can be represented is 33337076 so your a will actually be 33339076, not 33339077.

    You may notice that (33339076/b)%c is 2.98713 while (33339077/b)%c==3.0068869 (even when using more accurate arithmetic). When flooring one comes out to 2 and the other 3.

    To fix your problem use f64 throughout your code instead of f32. Note that storing a as a f32 at any point will introduce the inaccuracy, converting to f64 just before calling your function will be too late, the data is already lost.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search