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}