From edef0fba35053cee4bb66359034fcf3086e03225 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 13:55:36 +1200 Subject: [PATCH 1/8] Fix #9256 --- .../src/simplify/expr/mod.rs | 58 +++++-------- .../src/simplify/expr/tests.rs | 16 ++++ crates/swc_ecma_utils/src/number.rs | 86 +++++++++++++++++++ 3 files changed, 124 insertions(+), 36 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index b20047df3828..4f1aabac01c0 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -13,8 +13,10 @@ use swc_ecma_transforms_base::{ perf::{cpu_count, Parallel, ParallelExt}, }; use swc_ecma_utils::{ - is_literal, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, - ObjectType, StringType, SymbolType, UndefinedType, Value, + is_literal, + number::{ToJsInt32, ToJsUint32}, + prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, + StringType, SymbolType, UndefinedType, Value, }; use swc_ecma_visit::{as_folder, standard_only_visit_mut, VisitMut, VisitMutWith}; use Value::{Known, Unknown}; @@ -633,13 +635,6 @@ impl SimplifyExpr { // Bit shift operations op!("<<") | op!(">>") | op!(">>>") => { - /// Uses a method for treating a double as 32bits that is - /// equivalent to how JavaScript would convert a - /// number before applying a bit operation. - fn js_convert_double_to_bits(d: f64) -> i32 { - ((d.floor() as i64) & 0xffff_ffff) as i32 - } - fn try_fold_shift( ctx: &ExprCtx, op: BinaryOp, @@ -655,36 +650,27 @@ impl SimplifyExpr { _ => unreachable!(), }; - // only the lower 5 bits are used when shifting, so don't do anything - // if the shift amount is outside [0,32) - if !(0.0..32.0).contains(&rv) { - return Unknown; - } - - let rv_int = rv as i32; - if rv_int as f64 != rv { - unimplemented!("error reporting: FRACTIONAL_BITWISE_OPERAND") - // report(FRACTIONAL_BITWISE_OPERAND, right.span()); - // return n; - } - - if lv.floor() != lv { - unimplemented!("error reporting: FRACTIONAL_BITWISE_OPERAND") - // report(FRACTIONAL_BITWISE_OPERAND, left.span()); - // return n; - } + Known(match op { + op!("<<") => { + // https://262.ecma-international.org/5.1/#sec-11.7.1 + let lnum = lv.to_js_int32(); + let rnum = rv.to_js_uint32(); - let bits = js_convert_double_to_bits(lv); + (lnum << (rnum & 0x1f)) as f64 + } + op!(">>") => { + // https://262.ecma-international.org/5.1/#sec-11.7.2 + let lnum = lv.to_js_int32(); + let rnum = rv.to_js_uint32(); - Known(match op { - op!("<<") => (bits << rv_int) as f64, - op!(">>") => (bits >> rv_int) as f64, + (lnum >> (rnum & 0x1f)) as f64 + } op!(">>>") => { - let res = bits as u32 >> rv_int as u32; - // JavaScript always treats the result of >>> as unsigned. - // We must force Java to do the same here. - // unimplemented!(">>> (Zerofill rshift)") - res as f64 + // https://262.ecma-international.org/5.1/#sec-11.7.3 + let lnum = lv.to_js_uint32(); + let rnum = rv.to_js_uint32(); + + (lnum >> (rnum & 0x1f)) as f64 } _ => unreachable!("Unknown bit operator {:?}", op), diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index ba3ad6f30150..face55350bb9 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -659,6 +659,22 @@ fn test_fold_bitwise_op2() { fold("x = 12 | NaN", "x=12"); } +#[test] +fn test_issue_9256() { + // Returns -2 prior to fix (Number.MAX_VALUE) + fold("1.7976931348623157e+308 << 1", "0"); + + // Isn't changed prior to fix + fold("1.7976931348623157e+308 << 1.7976931348623157e+308", "0"); + fold("1.7976931348623157e+308 >> 1.7976931348623157e+308", "0"); + + // Panics prior to fix (Number.MIN_VALUE) + fold("5e-324 >> 5e-324", "0"); + fold("5e-324 << 5e-324", "0"); + fold("5e-324 << 0", "0"); + fold("0 << 5e-324", "0"); +} + #[test] #[ignore] fn test_folding_mix_types_early() { diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 54d0ae892f38..9ac79815737d 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -9,3 +9,89 @@ impl ToJsString for f64 { buffer.format(*self).to_string() } } + +/// Implements [ToInt32] as defined in ECMAScript Section 9.5. +/// +/// [ToInt32]: https://262.ecma-international.org/5.1/#sec-9.5 +pub trait ToJsInt32 { + /// Converts `self` into a signed 32-bit integer as defined in + /// ECMAScript Section 9.5, [ToInt32]. + /// + /// [ToInt32]: https://262.ecma-international.org/5.1/#sec-9.5 + fn to_js_int32(&self) -> i32; +} + +impl ToJsInt32 for f64 { + fn to_js_int32(&self) -> i32 { + if self.is_nan() || self.is_infinite() || *self == 0.0 { + return 0; + } + + const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 + const TWO_31: f64 = i32::MAX as f64 + 1.0; // 2^31 + + let pos_int = self.signum() * self.abs().floor(); + let int32_bit = pos_int % TWO_32; + + (if int32_bit >= TWO_31 { + int32_bit - TWO_32 + } else { + int32_bit + }) as i32 + } +} + +/// Implements [ToUint32] as defined in ECMAScript Section 9.6. +/// +/// [ToUint32]: https://262.ecma-international.org/5.1/#sec-9.6 +pub trait ToJsUint32 { + /// Converts `self` into an unsigned 32-bit integer as defined in + /// ECMAScript Section 9.6, [ToUint32]. + /// + /// [ToUint32]: https://262.ecma-international.org/5.1/#sec-9.6 + fn to_js_uint32(&self) -> u32; +} + +impl ToJsUint32 for f64 { + fn to_js_uint32(&self) -> u32 { + if self.is_nan() || self.is_infinite() || *self == 0.0 { + return 0; + } + + const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 + + let pos_int = self.signum() * self.abs().floor(); + (pos_int % TWO_32) as u32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_js_int32() { + assert_eq!(f64::NAN.to_js_int32(), 0); + assert_eq!(f64::INFINITY.to_js_int32(), 0); + assert_eq!(f64::NEG_INFINITY.to_js_int32(), 0); + assert_eq!(0.0.to_js_int32(), 0); + + assert_eq!(f64::MIN.to_js_int32(), 0); + assert_eq!(f64::MAX.to_js_int32(), 0); + + assert_eq!(5.2.to_js_int32(), 5); + } + + #[test] + fn test_to_js_uint32() { + assert_eq!(f64::NAN.to_js_uint32(), 0); + assert_eq!(f64::INFINITY.to_js_uint32(), 0); + assert_eq!(f64::NEG_INFINITY.to_js_uint32(), 0); + assert_eq!(0.0.to_js_uint32(), 0); + + assert_eq!(f64::MIN.to_js_uint32(), 0); + assert_eq!(f64::MAX.to_js_uint32(), 0); + + assert_eq!(5.2.to_js_uint32(), 5); + } +} From e4195223ee2a6e58c1620817317b6f889a8d7072 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 14:11:55 +1200 Subject: [PATCH 2/8] `cargo fmt` --- crates/swc_ecma_utils/src/number.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 9ac79815737d..f502d4270352 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -81,7 +81,7 @@ mod tests { assert_eq!(5.2.to_js_int32(), 5); } - + #[test] fn test_to_js_uint32() { assert_eq!(f64::NAN.to_js_uint32(), 0); From 3054e0d5405f84c42035dfac861a0d26d08b24ea Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 14:48:13 +1200 Subject: [PATCH 3/8] Fix `to_js_uint32` not overflowing --- .../src/simplify/expr/tests.rs | 5 +++++ crates/swc_ecma_utils/src/number.rs | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index face55350bb9..bd2d57a28af4 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -673,6 +673,11 @@ fn test_issue_9256() { fold("5e-324 << 5e-324", "0"); fold("5e-324 << 0", "0"); fold("0 << 5e-324", "0"); + + // Wasn't broken prior, used to ensure overflows are handled correctly + fold("1 << 31", "-2147483648"); + fold("-8 >> 2", "-2"); + fold("-8 >>> 2", "1073741822"); } #[test] diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index f502d4270352..3ef6beb8f915 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -61,7 +61,13 @@ impl ToJsUint32 for f64 { const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 let pos_int = self.signum() * self.abs().floor(); - (pos_int % TWO_32) as u32 + let result = pos_int % TWO_32; + // Extra step: since `x as u32` doesn't overflow, we have to add if result is negative + (if result < 0.0 { + result + TWO_32 + } else { + result + }) as u32 } } @@ -92,6 +98,6 @@ mod tests { assert_eq!(f64::MIN.to_js_uint32(), 0); assert_eq!(f64::MAX.to_js_uint32(), 0); - assert_eq!(5.2.to_js_uint32(), 5); + assert_eq!((-8.0).to_js_uint32(), 4294967288); } } From 437f40b7a8f7e3c99ade488c473756e454017f6e Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 14:49:12 +1200 Subject: [PATCH 4/8] `cargo fmt` --- crates/swc_ecma_utils/src/number.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 3ef6beb8f915..33687199a6db 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -62,7 +62,8 @@ impl ToJsUint32 for f64 { let pos_int = self.signum() * self.abs().floor(); let result = pos_int % TWO_32; - // Extra step: since `x as u32` doesn't overflow, we have to add if result is negative + // Extra step: since `x as u32` doesn't overflow, we have to add if result is + // negative (if result < 0.0 { result + TWO_32 } else { From 1790d52784d51a69e4df7af3c6b32e7e2ca8cd77 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 14:59:37 +1200 Subject: [PATCH 5/8] Update `test_fold_bit_shifts` --- .../src/simplify/expr/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs index bd2d57a28af4..dc2bae680324 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/tests.rs @@ -747,9 +747,9 @@ fn test_fold_bit_shifts() { fold("x = 0xffffffff << 0", "x = -1"); fold("x = 0xffffffff << 4", "x = -16"); - fold_same("1 << 32"); - fold_same("1 << -1"); - fold_same("1 >> 32"); + fold("1 << 32", "1"); + fold("1 << -1", "-2147483648"); + fold("1 >> 32", "1"); } #[test] From 6565c288d1f4043c3b2be329444677a7bc07c9ea Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 17:28:10 +1200 Subject: [PATCH 6/8] Use new `JsNumber` API and fix broken `as_int32` and `as_uint32` implementation --- .../src/simplify/expr/mod.rs | 29 +---- crates/swc_ecma_utils/src/number.rs | 115 ++++-------------- 2 files changed, 29 insertions(+), 115 deletions(-) diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index 1897bf5b70f0..837d215b875b 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -13,10 +13,6 @@ use swc_ecma_transforms_base::{ perf::{cpu_count, Parallel, ParallelExt}, }; use swc_ecma_utils::{ - is_literal, - number::{ToJsInt32, ToJsUint32}, - prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, - StringType, SymbolType, UndefinedType, Value, is_literal, number::JsNumber, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, }; @@ -651,29 +647,12 @@ impl SimplifyExpr { (Known(lv), Known(rv)) => (lv, rv), _ => unreachable!(), }; + let (lv, rv) = (JsNumber::from(lv), JsNumber::from(rv)); Known(match op { - op!("<<") => { - // https://262.ecma-international.org/5.1/#sec-11.7.1 - let lnum = lv.to_js_int32(); - let rnum = rv.to_js_uint32(); - - (lnum << (rnum & 0x1f)) as f64 - } - op!(">>") => { - // https://262.ecma-international.org/5.1/#sec-11.7.2 - let lnum = lv.to_js_int32(); - let rnum = rv.to_js_uint32(); - - (lnum >> (rnum & 0x1f)) as f64 - } - op!(">>>") => { - // https://262.ecma-international.org/5.1/#sec-11.7.3 - let lnum = lv.to_js_uint32(); - let rnum = rv.to_js_uint32(); - - (lnum >> (rnum & 0x1f)) as f64 - } + op!("<<") => *(lv << rv), + op!(">>") => *(lv >> rv), + op!(">>>") => *(lv.unsigned_shr(rv)), _ => unreachable!("Unknown bit operator {:?}", op), }) diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 05da20d350c8..ee47129859da 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -10,65 +10,6 @@ impl ToJsString for f64 { } } -/// Implements [ToInt32] as defined in ECMAScript Section 9.5. -/// -/// [ToInt32]: https://262.ecma-international.org/5.1/#sec-9.5 -pub trait ToJsInt32 { - /// Converts `self` into a signed 32-bit integer as defined in - /// ECMAScript Section 9.5, [ToInt32]. - /// - /// [ToInt32]: https://262.ecma-international.org/5.1/#sec-9.5 - fn to_js_int32(&self) -> i32; -} - -impl ToJsInt32 for f64 { - fn to_js_int32(&self) -> i32 { - if self.is_nan() || self.is_infinite() || *self == 0.0 { - return 0; - } - - const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 - const TWO_31: f64 = i32::MAX as f64 + 1.0; // 2^31 - - let pos_int = self.signum() * self.abs().floor(); - let int32_bit = pos_int % TWO_32; - - (if int32_bit >= TWO_31 { - int32_bit - TWO_32 - } else { - int32_bit - }) as i32 - } -} - -/// Implements [ToUint32] as defined in ECMAScript Section 9.6. -/// -/// [ToUint32]: https://262.ecma-international.org/5.1/#sec-9.6 -pub trait ToJsUint32 { - /// Converts `self` into an unsigned 32-bit integer as defined in - /// ECMAScript Section 9.6, [ToUint32]. - /// - /// [ToUint32]: https://262.ecma-international.org/5.1/#sec-9.6 - fn to_js_uint32(&self) -> u32; -} - -impl ToJsUint32 for f64 { - fn to_js_uint32(&self) -> u32 { - if self.is_nan() || self.is_infinite() || *self == 0.0 { - return 0; - } - - const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 - - let pos_int = self.signum() * self.abs().floor(); - let result = pos_int % TWO_32; - // Extra step: since `x as u32` doesn't overflow, we have to add if result is - // negative - (if result < 0.0 { - result + TWO_32 - } else { - result - }) as u32 #[derive(Debug, Clone, Copy, PartialEq)] pub struct JsNumber(f64); @@ -95,20 +36,40 @@ impl std::ops::Deref for JsNumber { impl JsNumber { // https://tc39.es/ecma262/#sec-toint32 fn as_int32(&self) -> i32 { - if !self.0.is_finite() { + if !self.0.is_finite() || self.0 == 0.0 { return 0; } - self.0.trunc() as i32 + const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 + const TWO_31: f64 = i32::MAX as f64 + 1.0; // 2^31 + + let pos_int = self.signum() * self.abs().floor(); + let int32_bit = pos_int % TWO_32; + + (if int32_bit >= TWO_31 { + int32_bit - TWO_32 + } else { + int32_bit + }) as i32 } // https://tc39.es/ecma262/#sec-touint32 fn as_uint32(&self) -> u32 { - if !self.0.is_finite() { + if !self.0.is_finite() || self.0 == 0.0 { return 0; } - self.0.trunc() as u32 + const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 + + let pos_int = self.signum() * self.abs().floor(); + let result = pos_int % TWO_32; + // Extra step: since `x as u32` doesn't overflow, we have to add if result is + // negative + (if result < 0.0 { + result + TWO_32 + } else { + result + }) as u32 } } @@ -251,33 +212,6 @@ impl std::ops::Not for JsNumber { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_to_js_int32() { - assert_eq!(f64::NAN.to_js_int32(), 0); - assert_eq!(f64::INFINITY.to_js_int32(), 0); - assert_eq!(f64::NEG_INFINITY.to_js_int32(), 0); - assert_eq!(0.0.to_js_int32(), 0); - - assert_eq!(f64::MIN.to_js_int32(), 0); - assert_eq!(f64::MAX.to_js_int32(), 0); - - assert_eq!(5.2.to_js_int32(), 5); - } - - #[test] - fn test_to_js_uint32() { - assert_eq!(f64::NAN.to_js_uint32(), 0); - assert_eq!(f64::INFINITY.to_js_uint32(), 0); - assert_eq!(f64::NEG_INFINITY.to_js_uint32(), 0); - assert_eq!(0.0.to_js_uint32(), 0); - - assert_eq!(f64::MIN.to_js_uint32(), 0); - assert_eq!(f64::MAX.to_js_uint32(), 0); - - assert_eq!((-8.0).to_js_uint32(), 4294967288); mod test_js_number { use super::*; @@ -297,6 +231,7 @@ mod test_js_number { assert_eq!(JsNumber(-0.0).as_uint32(), 0); assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0); assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0); + assert_eq!(JsNumber::from(-8.0).as_uint32(), 4294967288); } #[test] From 965b95b7b31748a5bde61381dd9d08fb398a0c2f Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 16 Jul 2024 19:22:16 +1200 Subject: [PATCH 7/8] `JsNumber::from` -> `JsNumber` --- crates/swc_ecma_utils/src/number.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index ee47129859da..5e301ebc68dc 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -231,7 +231,7 @@ mod test_js_number { assert_eq!(JsNumber(-0.0).as_uint32(), 0); assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0); assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0); - assert_eq!(JsNumber::from(-8.0).as_uint32(), 4294967288); + assert_eq!(JsNumber(-8.0).as_uint32(), 4294967288); } #[test] From d2554e62e77120a7ee67abedd343d6c32f599f1e Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 16 Jul 2024 15:25:03 +0800 Subject: [PATCH 8/8] Refactor: Simplifying the code --- crates/swc_ecma_utils/src/number.rs | 31 ++++------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 5e301ebc68dc..e3923d49b23e 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -36,40 +36,17 @@ impl std::ops::Deref for JsNumber { impl JsNumber { // https://tc39.es/ecma262/#sec-toint32 fn as_int32(&self) -> i32 { - if !self.0.is_finite() || self.0 == 0.0 { - return 0; - } - - const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 - const TWO_31: f64 = i32::MAX as f64 + 1.0; // 2^31 - - let pos_int = self.signum() * self.abs().floor(); - let int32_bit = pos_int % TWO_32; - - (if int32_bit >= TWO_31 { - int32_bit - TWO_32 - } else { - int32_bit - }) as i32 + self.as_uint32() as i32 } // https://tc39.es/ecma262/#sec-touint32 fn as_uint32(&self) -> u32 { - if !self.0.is_finite() || self.0 == 0.0 { + if !self.0.is_finite() { return 0; } - const TWO_32: f64 = u32::MAX as f64 + 1.0; // 2^32 - - let pos_int = self.signum() * self.abs().floor(); - let result = pos_int % TWO_32; - // Extra step: since `x as u32` doesn't overflow, we have to add if result is - // negative - (if result < 0.0 { - result + TWO_32 - } else { - result - }) as u32 + // pow(2, 32) = 4294967296 + self.0.trunc().rem_euclid(4294967296.0) as u32 } }