swc_ecma_utils/
number.rs

1/// <https://tc39.es/ecma262/#sec-numeric-types-number-tostring>
2pub trait ToJsString {
3    fn to_js_string(&self) -> String;
4}
5
6impl ToJsString for f64 {
7    fn to_js_string(&self) -> String {
8        let mut buffer = ryu_js::Buffer::new();
9        buffer.format(*self).to_string()
10    }
11}
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct JsNumber(f64);
15
16impl From<f64> for JsNumber {
17    fn from(v: f64) -> Self {
18        JsNumber(v)
19    }
20}
21
22impl From<JsNumber> for f64 {
23    fn from(v: JsNumber) -> Self {
24        v.0
25    }
26}
27
28impl std::ops::Deref for JsNumber {
29    type Target = f64;
30
31    fn deref(&self) -> &Self::Target {
32        &self.0
33    }
34}
35
36impl JsNumber {
37    // https://tc39.es/ecma262/#sec-toint32
38    fn as_int32(&self) -> i32 {
39        self.as_uint32() as i32
40    }
41
42    // https://tc39.es/ecma262/#sec-touint32
43    fn as_uint32(&self) -> u32 {
44        if !self.0.is_finite() {
45            return 0;
46        }
47
48        // pow(2, 32) = 4294967296
49        self.0.trunc().rem_euclid(4294967296.0) as u32
50    }
51}
52
53// JsNumber + JsNumber
54impl std::ops::Add<JsNumber> for JsNumber {
55    type Output = JsNumber;
56
57    fn add(self, rhs: JsNumber) -> Self::Output {
58        JsNumber(self.0 + rhs.0)
59    }
60}
61
62// JsNumber - JsNumber
63impl std::ops::Sub<JsNumber> for JsNumber {
64    type Output = JsNumber;
65
66    fn sub(self, rhs: JsNumber) -> Self::Output {
67        JsNumber(self.0 - rhs.0)
68    }
69}
70
71// JsNumber * JsNumber
72impl std::ops::Mul<JsNumber> for JsNumber {
73    type Output = JsNumber;
74
75    fn mul(self, rhs: JsNumber) -> Self::Output {
76        JsNumber(self.0 * rhs.0)
77    }
78}
79
80// JsNumber / JsNumber
81impl std::ops::Div<JsNumber> for JsNumber {
82    type Output = JsNumber;
83
84    fn div(self, rhs: JsNumber) -> Self::Output {
85        JsNumber(self.0 / rhs.0)
86    }
87}
88
89// JsNumber % JsNumber
90impl std::ops::Rem<JsNumber> for JsNumber {
91    type Output = JsNumber;
92
93    fn rem(self, rhs: JsNumber) -> Self::Output {
94        JsNumber(self.0 % rhs.0)
95    }
96}
97
98// JsNumber ** JsNumber
99impl JsNumber {
100    pub fn pow(self, rhs: JsNumber) -> JsNumber {
101        // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate
102        // https://github.com/rust-lang/rust/issues/60468
103        if rhs.0.is_nan() {
104            return JsNumber(f64::NAN);
105        }
106
107        if self.0.abs() == 1f64 && rhs.0.is_infinite() {
108            return JsNumber(f64::NAN);
109        }
110
111        JsNumber(self.0.powf(rhs.0))
112    }
113}
114
115// JsNumber << JsNumber
116// https://tc39.es/ecma262/#sec-numeric-types-number-leftShift
117impl std::ops::Shl<JsNumber> for JsNumber {
118    type Output = JsNumber;
119
120    fn shl(self, rhs: JsNumber) -> Self::Output {
121        JsNumber(self.as_int32().wrapping_shl(rhs.as_uint32()) as f64)
122    }
123}
124
125// JsNumber >> JsNumber
126// https://tc39.es/ecma262/#sec-numeric-types-number-signedRightShift
127impl std::ops::Shr<JsNumber> for JsNumber {
128    type Output = JsNumber;
129
130    fn shr(self, rhs: JsNumber) -> Self::Output {
131        JsNumber((self.as_int32()).wrapping_shr(rhs.as_uint32()) as f64)
132    }
133}
134
135// JsNumber >>> JsNumber
136// https://tc39.es/ecma262/#sec-numeric-types-number-unsignedRightShift
137impl JsNumber {
138    pub fn unsigned_shr(self, rhs: JsNumber) -> JsNumber {
139        JsNumber((self.as_uint32()).wrapping_shr(rhs.as_uint32()) as f64)
140    }
141}
142
143// JsNumber | JsNumber
144// https://tc39.es/ecma262/#sec-numberbitwiseop
145impl std::ops::BitOr<JsNumber> for JsNumber {
146    type Output = JsNumber;
147
148    fn bitor(self, rhs: JsNumber) -> Self::Output {
149        JsNumber((self.as_int32() | rhs.as_int32()) as f64)
150    }
151}
152
153// JsNumber & JsNumber
154// https://tc39.es/ecma262/#sec-numberbitwiseop
155impl std::ops::BitAnd<JsNumber> for JsNumber {
156    type Output = JsNumber;
157
158    fn bitand(self, rhs: JsNumber) -> Self::Output {
159        JsNumber((self.as_int32() & rhs.as_int32()) as f64)
160    }
161}
162
163// JsNumber ^ JsNumber
164// https://tc39.es/ecma262/#sec-numberbitwiseop
165impl std::ops::BitXor<JsNumber> for JsNumber {
166    type Output = JsNumber;
167
168    fn bitxor(self, rhs: JsNumber) -> Self::Output {
169        JsNumber((self.as_int32() ^ rhs.as_int32()) as f64)
170    }
171}
172
173// - JsNumber
174impl std::ops::Neg for JsNumber {
175    type Output = JsNumber;
176
177    fn neg(self) -> Self::Output {
178        JsNumber(-self.0)
179    }
180}
181
182// ~ JsNumber
183impl std::ops::Not for JsNumber {
184    type Output = JsNumber;
185
186    fn not(self) -> Self::Output {
187        JsNumber(!(self.as_int32()) as f64)
188    }
189}
190
191#[cfg(test)]
192mod test_js_number {
193    use super::*;
194
195    #[test]
196    fn test_as_int32() {
197        assert_eq!(JsNumber(f64::NAN).as_int32(), 0);
198        assert_eq!(JsNumber(0.0).as_int32(), 0);
199        assert_eq!(JsNumber(-0.0).as_int32(), 0);
200        assert_eq!(JsNumber(f64::INFINITY).as_int32(), 0);
201        assert_eq!(JsNumber(f64::NEG_INFINITY).as_int32(), 0);
202    }
203
204    #[test]
205    fn test_as_uint32() {
206        assert_eq!(JsNumber(f64::NAN).as_uint32(), 0);
207        assert_eq!(JsNumber(0.0).as_uint32(), 0);
208        assert_eq!(JsNumber(-0.0).as_uint32(), 0);
209        assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0);
210        assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0);
211        assert_eq!(JsNumber(-8.0).as_uint32(), 4294967288);
212    }
213
214    #[test]
215    fn test_add() {
216        assert_eq!(JsNumber(1.0) + JsNumber(2.0), JsNumber(3.0));
217
218        assert!((JsNumber(1.0) + JsNumber(f64::NAN)).is_nan());
219        assert!((JsNumber(f64::NAN) + JsNumber(1.0)).is_nan());
220        assert!((JsNumber(f64::NAN) + JsNumber(f64::NAN)).is_nan());
221        assert!((JsNumber(f64::INFINITY) + JsNumber(f64::NEG_INFINITY)).is_nan());
222        assert!((JsNumber(f64::NEG_INFINITY) + JsNumber(f64::INFINITY)).is_nan());
223
224        assert_eq!(
225            JsNumber(f64::INFINITY) + JsNumber(1.0),
226            JsNumber(f64::INFINITY)
227        );
228        assert_eq!(
229            JsNumber(f64::NEG_INFINITY) + JsNumber(1.0),
230            JsNumber(f64::NEG_INFINITY)
231        );
232        assert_eq!(JsNumber(-0.0) + JsNumber(0.0), JsNumber(-0.0));
233    }
234}