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
Well, your JS function
Doesn’t define
a
,b
, orc
. 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
Assuming that
f32
is a single precision IEEE float andi32
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?
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:gives:
Playground
The maximum safe integer for a
f32
is16777215
. Integers above this value can not be stored exactly. See this rust code:The nearest value to
33339077
that can be represented is33337076
so youra
will actually be 33339076, not 33339077.You may notice that
(33339076/b)%c
is2.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 off32
. Note that storinga
as af32
at any point will introduce the inaccuracy, converting tof64
just before calling your function will be too late, the data is already lost.