@@ -165,6 +165,30 @@ fn is_power_of_two<F: Float>(x: F) -> bool {
165165 x. is_finite_non_zero ( ) && x. abs ( ) . scalbn ( -x. ilogb ( ) ) == F :: from_u128 ( 1 ) . value
166166}
167167
168+ /// Compute the ULP of the input using a definition from:
169+ /// Jean-Michel Muller. On the definition of ulp(x). [Research Report] RR-5504,
170+ /// LIP RR-2005-09, INRIA, LIP. 2005, pp.16. inria-00070503
171+ fn harrison_ulp < F : Float > ( x : F ) -> F {
172+ match x. category ( ) {
173+ Category :: NaN => return F :: qnan ( None ) ,
174+ Category :: Infinity => return F :: INFINITY ,
175+ Category :: Zero => return F :: SMALLEST ,
176+ Category :: Normal => { /* fall through */ }
177+ }
178+
179+ if x. is_denormal ( ) || x. is_smallest_normalized ( ) {
180+ return F :: SMALLEST ;
181+ }
182+
183+ // Match LLVM in not considering negative powers of two.
184+ let mut exp = x. ilogb ( ) ;
185+ if !x. is_negative ( ) && is_power_of_two ( x) {
186+ exp -= 1 ;
187+ }
188+
189+ F :: from_u128 ( 1 ) . value . scalbn ( exp - ( F :: PRECISION as i32 - 1 ) )
190+ }
191+
168192impl < F : FloatConvert < Fallback < F > > > Float for DoubleFloat < F >
169193where
170194 Self : From < Fallback < F > > ,
@@ -565,12 +589,111 @@ where
565589 }
566590
567591 fn frexp_r ( self , exp : & mut ExpInt , round : Round ) -> Self {
568- let a = self . 0 . frexp_r ( exp, round) ;
569- let mut b = self . 1 ;
570- if self . category ( ) == Category :: Normal {
571- b = b. scalbn_r ( -* exp, round) ;
592+ // Get the unbiased exponent e of the number, where |self| = m * 2^e for m in [1.0, 2.0).
593+ * exp = self . ilogb ( ) ;
594+
595+ // For NaNs, quiet any signaling NaN and return the result, as per standard practice.
596+ if * exp == crate :: IEK_NAN {
597+ let mut quiet = self ;
598+ quiet. 0 = quiet. 0 . add_r ( F :: ZERO , Round :: NearestTiesToEven ) . value ;
599+ return quiet;
572600 }
573- DoubleFloat ( a, b)
601+
602+ // For infinity, return it unchanged. The exponent remains IEK_Inf.
603+ if * exp == crate :: IEK_INF {
604+ return self ;
605+ }
606+
607+ // For zero, the fraction is zero and the standard requires the exponent be 0.
608+ if * exp == crate :: IEK_ZERO {
609+ * exp = 0 ;
610+ return self ;
611+ }
612+
613+ let DoubleFloat ( hi, lo) = self ;
614+
615+ // frexp requires the fraction's absolute value to be in [0.5, 1.0).
616+ // ilogb provides an exponent for an absolute value in [1.0, 2.0).
617+ // Increment the exponent to ensure the fraction is in the correct range.
618+ * exp += 1 ;
619+
620+ let signs_disagree = hi. is_negative ( ) != lo. is_negative ( ) ;
621+ let mut second = lo;
622+ if self . category ( ) == Category :: Normal && lo. is_finite_non_zero ( ) {
623+ // The interpretation of Round::TowardZero depends on the sign of the combined
624+ // self rather than the sign of the component.
625+ let lo_rounding_mode = if round == Round :: TowardZero {
626+ if self . is_negative ( ) {
627+ Round :: TowardPositive
628+ } else {
629+ Round :: TowardNegative
630+ }
631+ } else if round == Round :: NearestTiesToAway && signs_disagree && * exp > 0 {
632+ // For Round::NearestTiesToAway, we face a similar problem. If signs disagree,
633+ // Lo is a correction *toward* zero relative to Hi. Rounding Lo
634+ // "away from zero" based on its own sign would move the value in the
635+ // wrong direction. As a safe proxy, we use Round::NearestTiesToEven, which is
636+ // direction-agnostic. We only need to bother with this if Lo is scaled
637+ // down.
638+ Round :: NearestTiesToEven
639+ } else {
640+ round
641+ } ;
642+
643+ second = lo. scalbn_r ( -* exp, lo_rounding_mode) ;
644+
645+ // The Round::NearestTiesToEven proxy is correct most of the time, but it
646+ // differs from Round::NearestTiesToAway when the scaled value of Lo is an
647+ // exact midpoint.
648+ // NOTE: This is morally equivalent to roundTiesTowardZero.
649+ if round == Round :: NearestTiesToAway && lo_rounding_mode == Round :: NearestTiesToEven {
650+ // Re-scale the result back to check if rounding occurred.
651+ let recomposed_lo = second. scalbn_r ( * exp, Round :: NearestTiesToEven ) ;
652+ if recomposed_lo != lo {
653+ // RoundingError tells us which direction we rounded:
654+ // - RoundingError > 0: we rounded up.
655+ // - RoundingError < 0: we down up.
656+ let rounding_error = ( recomposed_lo - lo) . value ;
657+ // Determine if scalbn(Lo, -Exp) landed exactly on a midpoint.
658+ // We do this by checking if the absolute rounding error is exactly
659+ // half a ULP of the result.
660+ let ulp_of_second = harrison_ulp ( second) ;
661+ let scaled_ulp_of_second = ulp_of_second. scalbn_r ( * exp - 1 , Round :: NearestTiesToEven ) ;
662+ let is_midpoint = rounding_error. abs ( ) == scaled_ulp_of_second;
663+ let rounded_lo_away = second. is_negative ( ) == rounding_error. is_negative ( ) ;
664+ // The sign of Hi and Lo disagree and we rounded Lo away: we must
665+ // decrease the magnitude of Second to increase the magnitude
666+ // First+Second.
667+ if is_midpoint && rounded_lo_away {
668+ second = if second. is_negative ( ) {
669+ second. next_up ( ) . value
670+ } else {
671+ second. next_down ( ) . value
672+ } ;
673+ }
674+ }
675+ }
676+
677+ // Handle a tricky edge case where self is slightly less than a power of two
678+ // (e.g., self = 2^k - epsilon). In this situation:
679+ // 1. Hi is 2^k, and Lo is a small negative value -epsilon.
680+ // 2. ilogb(self) correctly returns k-1.
681+ // 3. Our initial Exp becomes (k-1) + 1 = k.
682+ // 4. Scaling Hi (2^k) by 2^-k would yield a magnitude of 1.0 and
683+ // scaling Lo by 2^-k would yield zero. This would make the result 1.0
684+ // which is an invalid fraction, as the required interval is [0.5, 1.0).
685+ // We detect this specific case by checking if Hi is a power of two and if
686+ // the scaled Lo underflowed to zero. The fix: Increment Exp to k+1. This
687+ // adjusts the scale factor, causing Hi to be scaled to 0.5, which is a
688+ // valid fraction.
689+ if second. is_zero ( ) && signs_disagree && is_power_of_two ( hi) {
690+ * exp += 1 ;
691+ }
692+ }
693+
694+ let first = hi. scalbn_r ( -* exp, round) ;
695+
696+ DoubleFloat ( first, second)
574697 }
575698}
576699
@@ -788,4 +911,122 @@ mod tests {
788911 assert ! ( quiet_nan. round_to_integral( Round :: NearestTiesToAway ) . value. bitwise_eq( quiet_nan) ) ;
789912 assert ! ( quiet_nan. round_to_integral( Round :: NearestTiesToEven ) . value. bitwise_eq( quiet_nan) ) ;
790913 }
914+
915+ #[ test]
916+ fn ppc_double_double_frexp ( ) {
917+ let double_from_f64 = |f : f64 | ieee:: Double :: from_bits ( f. to_bits ( ) . into ( ) ) ;
918+ let dd = |hi, lo| DoubleFloat ( double_from_f64 ( hi) , double_from_f64 ( lo) ) ;
919+
920+ let frexp_test_cases = [
921+ // Input: +infinity
922+ TestCase :: new ( dd ( f64:: INFINITY , 0.0 ) ) . with_all ( ( dd ( f64:: INFINITY , 0.0 ) , ExpInt :: MAX ) ) ,
923+ // Input: -infinity
924+ TestCase :: new ( dd ( f64:: NEG_INFINITY , 0.0 ) ) . with_all ( ( dd ( f64:: NEG_INFINITY , 0.0 ) , ExpInt :: MAX ) ) ,
925+ // Input: 2^-1074
926+ TestCase :: new ( dd ( f64:: from_bits ( 1 ) , 0.0 ) ) . with_all ( ( dd ( 0.5 , 0.0 ) , -1073 ) ) ,
927+ // Input: (2^1, -2^-1073 + -2^-1074)
928+ TestCase :: new ( dd ( 2.0 , -3.0 * f64:: from_bits ( 1 ) ) )
929+ . with_all ( ( dd ( 1.0 , -2.0 * f64:: from_bits ( 1 ) ) , 1 ) )
930+ . with ( Round :: NearestTiesToAway , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1 ) )
931+ . with ( Round :: TowardPositive , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1 ) ) ,
932+ // Input: (2^1, -2^-1073)
933+ TestCase :: new ( dd ( 2.0 , -2.0 * f64:: from_bits ( 1 ) ) ) . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1 ) ) ,
934+ // Input: (2^1, -2^-1074)
935+ TestCase :: new ( dd ( 2.0 , -f64:: from_bits ( 1 ) ) )
936+ . with_all ( ( dd ( 0.5 , -0.0 ) , 2 ) )
937+ . with ( Round :: TowardNegative , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1 ) )
938+ . with ( Round :: TowardZero , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1 ) ) ,
939+ // Input: (2^2, -2^-1072 + -2^-1073 + -2^-1074)
940+ TestCase :: new ( dd ( 4.0 , -7.0 * f64:: from_bits ( 1 ) ) )
941+ . with_all ( ( dd ( 1.0 , -2.0 * f64:: from_bits ( 1 ) ) , 2 ) )
942+ . with ( Round :: TowardPositive , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) ) ,
943+ // Input: (2^2, -2^-1072 + -2^-1073)
944+ TestCase :: new ( dd ( 4.0 , -6.0 * f64:: from_bits ( 1 ) ) )
945+ . with_all ( ( dd ( 1.0 , -2.0 * f64:: from_bits ( 1 ) ) , 2 ) )
946+ . with ( Round :: NearestTiesToAway , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) )
947+ . with ( Round :: TowardPositive , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) ) ,
948+ // Input: (2^2, -2^-1072 + -2^-1074)
949+ TestCase :: new ( dd ( 4.0 , -5.0 * f64:: from_bits ( 1 ) ) )
950+ . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) )
951+ . with ( Round :: TowardNegative , ( dd ( 1.0 , -2.0 * f64:: from_bits ( 1 ) ) , 2 ) )
952+ . with ( Round :: TowardZero , ( dd ( 1.0 , -2.0 * f64:: from_bits ( 1 ) ) , 2 ) ) ,
953+ // Input: (2^2, -2^-1072)
954+ TestCase :: new ( dd ( 4.0 , -4.0 * f64:: from_bits ( 1 ) ) ) . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) ) ,
955+ // Input: (2^2, -2^-1073 + -2^-1074)
956+ TestCase :: new ( dd ( 4.0 , -3.0 * f64:: from_bits ( 1 ) ) )
957+ . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) )
958+ . with ( Round :: TowardPositive , ( dd ( 0.5 , -0.0 ) , 3 ) ) ,
959+ // Input: (2^2, -2^-1073)
960+ TestCase :: new ( dd ( 4.0 , -2.0 * f64:: from_bits ( 1 ) ) )
961+ . with_all ( ( dd ( 0.5 , -0.0 ) , 3 ) )
962+ . with ( Round :: TowardNegative , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) )
963+ . with ( Round :: TowardZero , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) ) ,
964+ // Input: (2^2, -2^-1074)
965+ TestCase :: new ( dd ( 4.0 , -f64:: from_bits ( 1 ) ) )
966+ . with_all ( ( dd ( 0.5 , -0.0 ) , 3 ) )
967+ . with ( Round :: TowardNegative , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) )
968+ . with ( Round :: TowardZero , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 2 ) ) ,
969+ // Input: 3+3*2^-53 canonicalized to (3+2^-51, -2^-53)
970+ // Output: 0.75+0.75*2^-53 canonicalized to (.75+2^-53, -2^-55)
971+ TestCase :: new ( dd ( f64:: from_bits ( 0x4008_0000_0000_0001 ) , -2.0f64 . powi ( -53 ) ) )
972+ . with_all ( ( dd ( f64:: from_bits ( 0x3fe8_0000_0000_0001 ) , -2.0f64 . powi ( -55 ) ) , 2 ) ) ,
973+ // Input: (2^1021+2^969, 2^968-2^915)
974+ TestCase :: new ( dd ( f64:: from_bits ( 0x7fc0_0000_0000_0001 ) , f64:: from_bits ( 0x7c6f_ffff_ffff_ffff ) ) )
975+ . with_all ( ( dd ( f64:: from_bits ( 0x3fe0_0000_0000_0001 ) , f64:: from_bits ( 0x3c8f_ffff_ffff_ffff ) ) , 1022 ) ) ,
976+ // Input: (2^1023, -2^-1)
977+ TestCase :: new ( dd ( 2.0f64 . powi ( 1023 ) , -0.5 ) ) . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 << 50 ) ) , 1023 ) ) ,
978+ // Input: (2^1023, -2^-51)
979+ TestCase :: new ( dd ( 2.0f64 . powi ( 1023 ) , -2.0f64 . powi ( -51 ) ) ) . with_all ( ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1023 ) ) ,
980+ // Input: (2^1023, -2^-52)
981+ TestCase :: new ( dd ( 2.0f64 . powi ( 1023 ) , -2.0f64 . powi ( -52 ) ) )
982+ . with_all ( ( dd ( 0.5 , -0.0 ) , 1024 ) )
983+ . with ( Round :: TowardNegative , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1023 ) )
984+ . with ( Round :: TowardZero , ( dd ( 1.0 , -f64:: from_bits ( 1 ) ) , 1023 ) ) ,
985+ // Input: (2^1023, 2^-1074)
986+ TestCase :: new ( dd ( 2.0f64 . powi ( 1023 ) , f64:: from_bits ( 1 ) ) )
987+ . with_all ( ( dd ( 0.5 , 0.0 ) , 1024 ) )
988+ . with ( Round :: TowardPositive , ( dd ( 0.5 , f64:: from_bits ( 1 ) ) , 1024 ) ) ,
989+ // Input: (2^1024-2^971, 2^970-2^918)
990+ TestCase :: new ( DoubleDouble :: largest ( ) )
991+ . with_all ( ( dd ( f64:: from_bits ( 0x3fef_ffff_ffff_ffff ) , f64:: from_bits ( 0x3c8f_ffff_ffff_fffe ) ) , 1024 ) ) ,
992+ ] ;
993+
994+ let negate = |test_case : TestCase < DoubleDouble , ( DoubleDouble , ExpInt ) > | TestCase {
995+ input : -test_case. input ,
996+ nearest_ties_to_even : test_case. nearest_ties_to_even . map ( |( v, e) | ( -v, e) ) ,
997+ toward_positive : test_case. toward_negative . map ( |( v, e) | ( -v, e) ) ,
998+ toward_negative : test_case. toward_positive . map ( |( v, e) | ( -v, e) ) ,
999+ toward_zero : test_case. toward_zero . map ( |( v, e) | ( -v, e) ) ,
1000+ nearest_ties_to_away : test_case. nearest_ties_to_away . map ( |( v, e) | ( -v, e) ) ,
1001+ } ;
1002+
1003+ let mut actual_exp = 0 ;
1004+
1005+ for case in frexp_test_cases. iter ( ) . flat_map ( |v| [ * v, negate ( * v) ] ) {
1006+ if let Some ( ( expected, expected_exp) ) = case. nearest_ties_to_even {
1007+ assert_eq ! ( case. input. frexp_r( & mut actual_exp, Round :: NearestTiesToEven ) , expected) ;
1008+ assert_eq ! ( expected_exp, actual_exp) ;
1009+ }
1010+
1011+ if let Some ( ( expected, expected_exp) ) = case. nearest_ties_to_away {
1012+ assert_eq ! ( case. input. frexp_r( & mut actual_exp, Round :: NearestTiesToAway ) , expected) ;
1013+ assert_eq ! ( expected_exp, actual_exp) ;
1014+ }
1015+
1016+ if let Some ( ( expected, expected_exp) ) = case. toward_positive {
1017+ assert_eq ! ( case. input. frexp_r( & mut actual_exp, Round :: TowardPositive ) , expected) ;
1018+ assert_eq ! ( expected_exp, actual_exp) ;
1019+ }
1020+
1021+ if let Some ( ( expected, expected_exp) ) = case. toward_negative {
1022+ assert_eq ! ( case. input. frexp_r( & mut actual_exp, Round :: TowardNegative ) , expected) ;
1023+ assert_eq ! ( expected_exp, actual_exp) ;
1024+ }
1025+
1026+ if let Some ( ( expected, expected_exp) ) = case. toward_zero {
1027+ assert_eq ! ( case. input. frexp_r( & mut actual_exp, Round :: TowardZero ) , expected) ;
1028+ assert_eq ! ( expected_exp, actual_exp) ;
1029+ }
1030+ }
1031+ }
7911032}
0 commit comments