@@ -224,33 +224,67 @@ impl Ratio<BigInt> {
224
224
225
225
/* Comparisons */
226
226
227
- // comparing a/b and c/d is the same as comparing a*d and b*c, so we
228
- // abstract that pattern. The following macro takes a trait and either
229
- // a comma-separated list of "method name -> return value" or just
230
- // "method name" (return value is bool in that case)
231
- macro_rules! cmp_impl {
232
- ( impl $imp: ident, $( $method: ident) ,+) => {
233
- cmp_impl!( impl $imp, $( $method -> bool ) ,+) ;
234
- } ;
235
- // return something other than a Ratio<T>
236
- ( impl $imp: ident, $( $method: ident -> $res: ty) ,* ) => {
237
- impl <T > $imp for Ratio <T > where
238
- T : Clone + Mul <T , Output = T > + $imp
239
- {
240
- $(
241
- #[ inline]
242
- fn $method( & self , other: & Ratio <T >) -> $res {
243
- ( self . numer. clone( ) * other. denom. clone( ) ) . $method ( & ( self . denom. clone( ) * other. numer. clone( ) ) )
227
+ // Mathematically, comparing a/b and c/d is the same as comparing a*d and b*c, but it's very easy
228
+ // for those multiplications to overflow fixed-size integers, so we need to take care.
229
+
230
+ impl < T : Clone + Integer > Ord for Ratio < T > {
231
+ #[ inline]
232
+ fn cmp ( & self , other : & Self ) -> cmp:: Ordering {
233
+ // With equal denominators, the numerators can be directly compared
234
+ if self . denom == other. denom {
235
+ let ord = self . numer . cmp ( & other. numer ) ;
236
+ return if self . denom < T :: zero ( ) { ord. reverse ( ) } else { ord } ;
237
+ }
238
+
239
+ // With equal numerators, the denominators can be inversely compared
240
+ if self . numer == other. numer {
241
+ let ord = self . denom . cmp ( & other. denom ) ;
242
+ return if self . numer < T :: zero ( ) { ord } else { ord. reverse ( ) } ;
243
+ }
244
+
245
+ // Unfortunately, we don't have CheckedMul to try. That could sometimes avoid all the
246
+ // division below, or even always avoid it for BigInt and BigUint.
247
+ // FIXME- future breaking change to add Checked* to Integer?
248
+
249
+ // Compare as floored integers and remainders
250
+ let ( self_int, self_rem) = self . numer . div_mod_floor ( & self . denom ) ;
251
+ let ( other_int, other_rem) = other. numer . div_mod_floor ( & other. denom ) ;
252
+ match self_int. cmp ( & other_int) {
253
+ cmp:: Ordering :: Greater => cmp:: Ordering :: Greater ,
254
+ cmp:: Ordering :: Less => cmp:: Ordering :: Less ,
255
+ cmp:: Ordering :: Equal => {
256
+ match ( self_rem. is_zero ( ) , other_rem. is_zero ( ) ) {
257
+ ( true , true ) => cmp:: Ordering :: Equal ,
258
+ ( true , false ) => cmp:: Ordering :: Less ,
259
+ ( false , true ) => cmp:: Ordering :: Greater ,
260
+ ( false , false ) => {
261
+ // Compare the reciprocals of the remaining fractions in reverse
262
+ let self_recip = Ratio :: new_raw ( self . denom . clone ( ) , self_rem) ;
263
+ let other_recip = Ratio :: new_raw ( other. denom . clone ( ) , other_rem) ;
264
+ self_recip. cmp ( & other_recip) . reverse ( )
265
+ }
244
266
}
245
- ) *
267
+ } ,
246
268
}
247
- } ;
269
+ }
270
+ }
271
+
272
+ impl < T : Clone + Integer > PartialOrd for Ratio < T > {
273
+ #[ inline]
274
+ fn partial_cmp ( & self , other : & Self ) -> Option < cmp:: Ordering > {
275
+ Some ( self . cmp ( other) )
276
+ }
248
277
}
249
- cmp_impl ! ( impl PartialEq , eq, ne) ;
250
- cmp_impl ! ( impl PartialOrd , lt -> bool , gt -> bool , le -> bool , ge -> bool ,
251
- partial_cmp -> Option <cmp:: Ordering >) ;
252
- cmp_impl ! ( impl Eq , ) ;
253
- cmp_impl ! ( impl Ord , cmp -> cmp:: Ordering ) ;
278
+
279
+ impl < T : Clone + Integer > PartialEq for Ratio < T > {
280
+ #[ inline]
281
+ fn eq ( & self , other : & Self ) -> bool {
282
+ self . cmp ( other) == cmp:: Ordering :: Equal
283
+ }
284
+ }
285
+
286
+ impl < T : Clone + Integer > Eq for Ratio < T > { }
287
+
254
288
255
289
macro_rules! forward_val_val_binop {
256
290
( impl $imp: ident, $method: ident) => {
@@ -597,6 +631,43 @@ mod test {
597
631
assert ! ( _1 >= _0 && !( _0 >= _1) ) ;
598
632
}
599
633
634
+ #[ test]
635
+ fn test_cmp_overflow ( ) {
636
+ use std:: cmp:: Ordering ;
637
+
638
+ // issue #7 example:
639
+ let big = Ratio :: new ( 128u8 , 1 ) ;
640
+ let small = big. recip ( ) ;
641
+ assert ! ( big > small) ;
642
+
643
+ // try a few that are closer together
644
+ // (some matching numer, some matching denom, some neither)
645
+ let ratios = vec ! [
646
+ Ratio :: new( 125_i8 , 127_i8 ) ,
647
+ Ratio :: new( 63_i8 , 64_i8 ) ,
648
+ Ratio :: new( 124_i8 , 125_i8 ) ,
649
+ Ratio :: new( 125_i8 , 126_i8 ) ,
650
+ Ratio :: new( 126_i8 , 127_i8 ) ,
651
+ Ratio :: new( 127_i8 , 126_i8 ) ,
652
+ ] ;
653
+
654
+ fn check_cmp ( a : Ratio < i8 > , b : Ratio < i8 > , ord : Ordering ) {
655
+ println ! ( "comparing {} and {}" , a, b) ;
656
+ assert_eq ! ( a. cmp( & b) , ord) ;
657
+ assert_eq ! ( b. cmp( & a) , ord. reverse( ) ) ;
658
+ }
659
+
660
+ for ( i, & a) in ratios. iter ( ) . enumerate ( ) {
661
+ check_cmp ( a, a, Ordering :: Equal ) ;
662
+ check_cmp ( -a, a, Ordering :: Less ) ;
663
+ for & b in & ratios[ i+1 ..] {
664
+ check_cmp ( a, b, Ordering :: Less ) ;
665
+ check_cmp ( -a, -b, Ordering :: Greater ) ;
666
+ check_cmp ( a. recip ( ) , b. recip ( ) , Ordering :: Greater ) ;
667
+ check_cmp ( -a. recip ( ) , -b. recip ( ) , Ordering :: Less ) ;
668
+ }
669
+ }
670
+ }
600
671
601
672
#[ test]
602
673
fn test_to_integer ( ) {
0 commit comments