Skip to main content

nanvm_lib/vm/number/
int32_coercion.rs

1use crate::vm::Number;
2
3/// The `modulo 2^32` step `ToInt32` shares with `ToUint32`
4/// (<https://tc39.es/ecma262/#sec-toint32> / <https://tc39.es/ecma262/#sec-touint32>):
5/// non-finite (`NaN`, `±Infinity`) becomes `+0`, otherwise the number is
6/// truncated toward zero and reduced to its non-negative remainder modulo
7/// `2^32`. Unlike integer `%`, `f64`'s `%` (IEEE 754 remainder, C's `fmod`)
8/// never rounds: for any two finite operands the true mathematical
9/// remainder is itself exactly representable in the same format, so this
10/// step introduces no error of its own even when `number.trunc()` is a huge
11/// magnitude — whatever rounding happened is already baked into `number`
12/// from when it became a `Number` via `ToNumber`, upstream of this function.
13fn modulo_2_32(number: f64) -> u32 {
14    if !number.is_finite() {
15        return 0;
16    }
17    const TWO_POW_32: f64 = 4_294_967_296.0;
18    let remainder = number.trunc() % TWO_POW_32;
19    let non_negative = if remainder < 0.0 {
20        remainder + TWO_POW_32
21    } else {
22        remainder
23    };
24    non_negative as u32
25}
26
27impl Number {
28    /// `ToInt32`. <https://tc39.es/ecma262/#sec-toint32>
29    pub fn to_int32(self) -> i32 {
30        modulo_2_32(self.0) as i32
31    }
32
33    /// `ToUint32`. <https://tc39.es/ecma262/#sec-touint32> — used by `<<`/`>>`
34    /// (for the shift-count operand) and `>>>` (for both operands, since it
35    /// has no signed form).
36    pub fn to_uint32(self) -> u32 {
37        modulo_2_32(self.0)
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use crate::vm::Number;
44
45    fn to_int32(v: f64) -> i32 {
46        Number::from(v).to_int32()
47    }
48
49    fn to_uint32(v: f64) -> u32 {
50        Number::from(v).to_uint32()
51    }
52
53    #[test]
54    fn non_finite_and_zero() {
55        for n in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0, -0.0] {
56            assert_eq!(to_int32(n), 0);
57            assert_eq!(to_uint32(n), 0);
58        }
59    }
60
61    #[test]
62    fn truncates_toward_zero() {
63        assert_eq!(to_int32(3.9), 3);
64        assert_eq!(to_int32(-3.9), -3);
65    }
66
67    #[test]
68    fn in_range_round_trips() {
69        assert_eq!(to_int32(42.0), 42);
70        assert_eq!(to_int32(-42.0), -42);
71        assert_eq!(to_uint32(42.0), 42);
72    }
73
74    #[test]
75    fn wraps_at_32_bit_boundary() {
76        // 2^31 is the first value ToInt32 reinterprets as negative; ToUint32
77        // keeps it as-is, since it never reinterprets the sign bit.
78        assert_eq!(to_int32(2_147_483_648.0), i32::MIN);
79        assert_eq!(to_uint32(2_147_483_648.0), 2_147_483_648);
80        // 2^32 - 1 wraps to -1 as a signed 32-bit value.
81        assert_eq!(to_int32(4_294_967_295.0), -1);
82        assert_eq!(to_uint32(4_294_967_295.0), 4_294_967_295);
83        // 2^32 itself reduces to 0.
84        assert_eq!(to_int32(4_294_967_296.0), 0);
85        assert_eq!(to_uint32(4_294_967_296.0), 0);
86    }
87}