@@ -10,71 +10,142 @@ pub trait ToInt32 {
1010impl ToInt32 for f64 {
1111 #[ expect( clippy:: float_cmp, clippy:: cast_possible_truncation, clippy:: cast_possible_wrap) ]
1212 fn to_int_32 ( & self ) -> i32 {
13- const SIGN_MASK : u64 = 0x8000_0000_0000_0000 ;
14- const EXPONENT_MASK : u64 = 0x7FF0_0000_0000_0000 ;
15- const SIGNIFICAND_MASK : u64 = 0x000F_FFFF_FFFF_FFFF ;
16- const HIDDEN_BIT : u64 = 0x0010_0000_0000_0000 ;
17- const PHYSICAL_SIGNIFICAND_SIZE : i32 = 52 ; // Excludes the hidden bit.
18- const SIGNIFICAND_SIZE : i32 = 53 ;
19-
20- const EXPONENT_BIAS : i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE ;
21- const DENORMAL_EXPONENT : i32 = -EXPONENT_BIAS + 1 ;
22-
23- fn is_denormal ( number : f64 ) -> bool {
24- ( number. to_bits ( ) & EXPONENT_MASK ) == 0
13+ #[ cfg( target_arch = "aarch64" ) ]
14+ {
15+ f64_to_int32_arm64 ( * self )
2516 }
17+ #[ cfg( not( target_arch = "aarch64" ) ) ]
18+ {
19+ f64_to_int32_generic ( * self )
20+ }
21+ }
22+ }
2623
27- fn exponent ( number : f64 ) -> i32 {
28- if is_denormal ( number) {
29- return DENORMAL_EXPONENT ;
30- }
24+ /// Converts a 64-bit floating point number to an `i32` using [`FJCVTZS`][FJCVTZS] instruction on ARM64.
25+ ///
26+ /// [FJCVTZS]: https://developer.arm.com/documentation/dui0801/h/A64-Floating-point-Instructions/FJCVTZS
27+ #[ cfg( target_arch = "aarch64" ) ]
28+ fn f64_to_int32_arm64 ( number : f64 ) -> i32 {
29+ if number. is_nan ( ) {
30+ return 0 ;
31+ }
32+ let ret: i32 ;
33+ // SAFETY: Number is not nan so no floating-point exception should throw.
34+ unsafe {
35+ std:: arch:: asm!(
36+ "fjcvtzs {dst:w}, {src:d}" ,
37+ src = in( vreg) number,
38+ dst = out( reg) ret,
39+ )
40+ }
41+ ret
42+ }
3143
32- let d64 = number. to_bits ( ) ;
33- let biased_e = ( ( d64 & EXPONENT_MASK ) >> PHYSICAL_SIGNIFICAND_SIZE ) as i32 ;
44+ /// Generic implementation of ToInt32 for non-ARM64 architectures
45+ #[ cfg( not( target_arch = "aarch64" ) ) ]
46+ fn f64_to_int32_generic ( number : f64 ) -> i32 {
47+ const SIGN_MASK : u64 = 0x8000_0000_0000_0000 ;
48+ const EXPONENT_MASK : u64 = 0x7FF0_0000_0000_0000 ;
49+ const SIGNIFICAND_MASK : u64 = 0x000F_FFFF_FFFF_FFFF ;
50+ const HIDDEN_BIT : u64 = 0x0010_0000_0000_0000 ;
51+ const PHYSICAL_SIGNIFICAND_SIZE : i32 = 52 ; // Excludes the hidden bit.
52+ const SIGNIFICAND_SIZE : i32 = 53 ;
53+
54+ const EXPONENT_BIAS : i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE ;
55+ const DENORMAL_EXPONENT : i32 = -EXPONENT_BIAS + 1 ;
56+
57+ fn is_denormal ( number : f64 ) -> bool {
58+ ( number. to_bits ( ) & EXPONENT_MASK ) == 0
59+ }
3460
35- biased_e - EXPONENT_BIAS
61+ fn exponent ( number : f64 ) -> i32 {
62+ if is_denormal ( number) {
63+ return DENORMAL_EXPONENT ;
3664 }
3765
38- fn significand ( number : f64 ) -> u64 {
39- let d64 = number. to_bits ( ) ;
40- let significand = d64 & SIGNIFICAND_MASK ;
66+ let d64 = number. to_bits ( ) ;
67+ let biased_e = ( ( d64 & EXPONENT_MASK ) >> PHYSICAL_SIGNIFICAND_SIZE ) as i32 ;
4168
42- if is_denormal ( number ) { significand } else { significand + HIDDEN_BIT }
43- }
69+ biased_e - EXPONENT_BIAS
70+ }
4471
45- fn sign ( number : f64 ) -> i64 {
46- if ( number. to_bits ( ) & SIGN_MASK ) == 0 { 1 } else { -1 }
47- }
72+ fn significand ( number : f64 ) -> u64 {
73+ let d64 = number. to_bits ( ) ;
74+ let significand = d64 & SIGNIFICAND_MASK ;
75+
76+ if is_denormal ( number) { significand } else { significand + HIDDEN_BIT }
77+ }
78+
79+ fn sign ( number : f64 ) -> i64 {
80+ if ( number. to_bits ( ) & SIGN_MASK ) == 0 { 1 } else { -1 }
81+ }
4882
49- let number = * self ;
83+ // NOTE: this also matches with negative zero
84+ if !number. is_finite ( ) || number == 0.0 {
85+ return 0 ;
86+ }
5087
51- // NOTE: this also matches with negative zero
52- if !number. is_finite ( ) || number == 0.0 {
88+ if number. is_finite ( ) && number <= f64:: from ( i32:: MAX ) && number >= f64:: from ( i32:: MIN ) {
89+ let i = number as i32 ;
90+ if f64:: from ( i) == number {
91+ return i;
92+ }
93+ }
94+
95+ let exponent = exponent ( number) ;
96+ let bits = if exponent < 0 {
97+ if exponent <= -SIGNIFICAND_SIZE {
5398 return 0 ;
5499 }
55100
56- if number. is_finite ( ) && number <= f64:: from ( i32:: MAX ) && number >= f64:: from ( i32:: MIN ) {
57- let i = number as i32 ;
58- if f64:: from ( i) == number {
59- return i;
60- }
101+ significand ( number) >> -exponent
102+ } else {
103+ if exponent > 31 {
104+ return 0 ;
61105 }
62106
63- let exponent = exponent ( number ) ;
64- let bits = if exponent < 0 {
65- if exponent <= - SIGNIFICAND_SIZE {
66- return 0 ;
67- }
107+ ( significand ( number ) << exponent) & 0xFFFF_FFFF
108+ } ;
109+
110+ ( sign ( number ) * ( bits as i64 ) ) as i32
111+ }
68112
69- significand ( number) >> -exponent
70- } else {
71- if exponent > 31 {
72- return 0 ;
73- }
113+ /// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm.
114+ ///
115+ /// [ToUint32]: https://tc39.es/ecma262/#sec-touint32
116+ pub trait ToUint32 {
117+ fn to_uint_32 ( & self ) -> u32 ;
118+ }
74119
75- ( significand ( number) << exponent) & 0xFFFF_FFFF
76- } ;
120+ impl ToUint32 for f64 {
121+ fn to_uint_32 ( & self ) -> u32 {
122+ self . to_int_32 ( ) as u32
123+ }
124+ }
125+
126+ #[ cfg( test) ]
127+ mod test {
128+ use super :: * ;
129+
130+ #[ test]
131+ fn f64_to_int32_conversion ( ) {
132+ assert_eq ! ( 0.0_f64 . to_int_32( ) , 0 ) ;
133+ assert_eq ! ( ( -0.0_f64 ) . to_int_32( ) , 0 ) ;
134+ assert_eq ! ( f64 :: NAN . to_int_32( ) , 0 ) ;
135+ assert_eq ! ( f64 :: INFINITY . to_int_32( ) , 0 ) ;
136+ assert_eq ! ( f64 :: NEG_INFINITY . to_int_32( ) , 0 ) ;
137+ assert_eq ! ( ( ( i64 :: from( i32 :: MAX ) + 1 ) as f64 ) . to_int_32( ) , i32 :: MIN ) ;
138+ assert_eq ! ( ( ( i64 :: from( i32 :: MIN ) - 1 ) as f64 ) . to_int_32( ) , i32 :: MAX ) ;
139+
140+ // Test edge cases with maximum safe integers
141+ assert_eq ! ( ( 9007199254740992.0_f64 ) . to_int_32( ) , 0 ) ; // 2^53
142+ assert_eq ! ( ( -9007199254740992.0_f64 ) . to_int_32( ) , 0 ) ; // -2^53
143+ }
77144
78- ( sign ( number) * ( bits as i64 ) ) as i32
145+ #[ test]
146+ fn f64_to_uint32_conversion ( ) {
147+ assert_eq ! ( 0.0_f64 . to_uint_32( ) , 0 ) ;
148+ assert_eq ! ( ( -1.0_f64 ) . to_uint_32( ) , 4294967295 ) ; // -1 as u32 is 2^32 - 1
149+ assert_eq ! ( f64 :: NAN . to_uint_32( ) , 0 ) ;
79150 }
80151}
0 commit comments