@@ -171,12 +171,14 @@ const ORIGINAL_MAX: u64 = 0;
171
171
/// into an integer count. Partial ordering is used for threshholding, also usually in the context
172
172
/// of percentiles.
173
173
pub trait Counter
174
- : num:: Num + num:: ToPrimitive + num:: FromPrimitive + Copy + PartialOrd < Self > {
174
+ : num:: Num + num:: ToPrimitive + num:: FromPrimitive + num:: Saturating + num:: CheckedSub
175
+ + num:: CheckedAdd + Copy + PartialOrd < Self > {
175
176
}
176
177
177
178
// auto-implement marker trait
178
179
impl < T > Counter for T
179
- where T : num:: Num + num:: ToPrimitive + num:: FromPrimitive + Copy + PartialOrd < T >
180
+ where T : num:: Num + num:: ToPrimitive + num:: FromPrimitive + num:: Saturating + num:: CheckedSub
181
+ + num:: CheckedAdd + Copy + PartialOrd < T >
180
182
{
181
183
}
182
184
@@ -455,13 +457,14 @@ impl<T: Counter> Histogram<T> {
455
457
for i in 0 ..source. len ( ) {
456
458
let other_count = source[ i] ;
457
459
if other_count != T :: zero ( ) {
458
- self [ i] = self [ i] + other_count;
460
+ self [ i] = self [ i] . saturating_add ( other_count) ;
459
461
// TODO unwrapping .to_u64()
460
- observed_other_total_count = observed_other_total_count + other_count. to_u64 ( ) . unwrap ( ) ;
462
+ observed_other_total_count = observed_other_total_count
463
+ . saturating_add ( other_count. to_u64 ( ) . unwrap ( ) ) ;
461
464
}
462
465
}
463
466
464
- self . total_count = self . total_count + observed_other_total_count;
467
+ self . total_count = self . total_count . saturating_add ( observed_other_total_count) ;
465
468
let mx = source. max ( ) ;
466
469
if mx > self . max ( ) {
467
470
self . update_max ( mx) ;
@@ -552,6 +555,9 @@ impl<T: Counter> Histogram<T> {
552
555
if other_count != T :: zero ( ) {
553
556
let other_value = other. value_for ( i) ;
554
557
if self . count_at ( other_value) . unwrap ( ) < other_count {
558
+ // TODO Perhaps we should saturating sub here? Or expose some form of
559
+ // pluggability so users could choose to error or saturate? Both seem useful.
560
+ // It's also sort of inconsistent with overflow, which now saturates.
555
561
return Err ( SubtractionError :: SubtrahendCountExceedsMinuendCount ) ;
556
562
}
557
563
self . alter_n ( other_value, other_count, false ) . expect ( "value should fit by now" ) ;
@@ -777,11 +783,19 @@ impl<T: Counter> Histogram<T> {
777
783
}
778
784
779
785
fn alter_n ( & mut self , value : u64 , count : T , add : bool ) -> Result < ( ) , ( ) > {
786
+ // TODO consider split out addition and subtraction cases; this isn't gaining much by
787
+ // unifying since we have to test all the cases anyway, and the TODO below is marking a case
788
+ // that might well be impossible but seems needed because of the (possibly false) symmetry
789
+ // with addition
790
+
791
+ // add=false is used by subtract(), which should have already aborted if underflow was
792
+ // possible
793
+
780
794
let success = if let Some ( c) = self . mut_at ( value) {
781
795
if add {
782
- * c = * c + count;
796
+ * c = ( * c ) . saturating_add ( count) ;
783
797
} else {
784
- * c = * c - count ;
798
+ * c = ( * c ) . checked_sub ( & count ) . expect ( "count underflow on subtraction" ) ;
785
799
}
786
800
true
787
801
} else {
@@ -798,9 +812,14 @@ impl<T: Counter> Histogram<T> {
798
812
{
799
813
let c = self . mut_at ( value) . expect ( "value should fit after resize" ) ;
800
814
if add {
801
- * c = * c + count;
815
+ // after resize, should be no possibility of overflow because this is a new slot
816
+ * c = ( * c) . checked_add ( & count) . expect ( "count overflow after resize" ) ;
802
817
} else {
803
- * c = * c - count;
818
+ // TODO Not sure this code path can ever be hit: if subtraction requires minuend
819
+ // count to exceed subtrahend count for a given value, we shouldn't ever need
820
+ // to resize to subtract.
821
+ // Anyway, at the very least, we know it shouldn't underflow.
822
+ * c = ( * c) . checked_sub ( & count) . expect ( "count underflow after resize" ) ;
804
823
}
805
824
}
806
825
@@ -809,9 +828,10 @@ impl<T: Counter> Histogram<T> {
809
828
810
829
self . update_min_max ( value) ;
811
830
if add {
812
- self . total_count = self . total_count + count. to_u64 ( ) . unwrap ( ) ;
831
+ self . total_count = self . total_count . saturating_add ( count. to_u64 ( ) . unwrap ( ) ) ;
813
832
} else {
814
- self . total_count = self . total_count - count. to_u64 ( ) . unwrap ( ) ;
833
+ self . total_count = self . total_count . checked_sub ( count. to_u64 ( ) . unwrap ( ) )
834
+ . expect ( "total count underflow on subtraction" ) ;
815
835
}
816
836
Ok ( ( ) )
817
837
}
@@ -1113,6 +1133,7 @@ impl<T: Counter> Histogram<T> {
1113
1133
1114
1134
let mut total_to_current_index: u64 = 0 ;
1115
1135
for i in 0 ..self . len ( ) {
1136
+ // TODO overflow
1116
1137
total_to_current_index = total_to_current_index + self [ i] . to_u64 ( ) . unwrap ( ) ;
1117
1138
if total_to_current_index >= count_at_percentile {
1118
1139
let value_at_index = self . value_for ( i) ;
@@ -1139,6 +1160,7 @@ impl<T: Counter> Histogram<T> {
1139
1160
}
1140
1161
1141
1162
let target_index = cmp:: min ( self . index_for ( value) , self . last ( ) ) ;
1163
+ // TODO overflow
1142
1164
let total_to_current_index =
1143
1165
( 0 ..( target_index + 1 ) ) . map ( |i| self [ i] ) . fold ( T :: zero ( ) , |t, v| t + v) ;
1144
1166
100.0 * total_to_current_index. to_f64 ( ) . unwrap ( ) / self . total_count as f64
@@ -1157,6 +1179,7 @@ impl<T: Counter> Histogram<T> {
1157
1179
pub fn count_between ( & self , low : u64 , high : u64 ) -> Result < T , ( ) > {
1158
1180
let low_index = self . index_for ( low) ;
1159
1181
let high_index = cmp:: min ( self . index_for ( high) , self . last ( ) ) ;
1182
+ // TODO overflow
1160
1183
Ok ( ( low_index..( high_index + 1 ) ) . map ( |i| self [ i] ) . fold ( T :: zero ( ) , |t, v| t + v) )
1161
1184
}
1162
1185
@@ -1229,6 +1252,7 @@ impl<T: Counter> Histogram<T> {
1229
1252
///
1230
1253
/// Note that the return value is capped at `u64::max_value()`.
1231
1254
pub fn median_equivalent ( & self , value : u64 ) -> u64 {
1255
+ // TODO isn't this just saturating?
1232
1256
match self . lowest_equivalent ( value) . overflowing_add ( self . equivalent_range ( value) >> 1 ) {
1233
1257
( _, of) if of => u64:: max_value ( ) ,
1234
1258
( v, _) => v,
@@ -1444,8 +1468,7 @@ impl <T: Counter> RestatState<T> {
1444
1468
fn on_nonzero_count ( & mut self , index : usize , count : T ) {
1445
1469
// TODO don't unwrap here; weird user Counter types may not work.
1446
1470
// Fix Counter types to just be u8-64?
1447
- // TODO this can wrap, but not sure there's much we can do about that. Saturating add maybe?
1448
- self . total_count += count. to_u64 ( ) . unwrap ( ) ;
1471
+ self . total_count = self . total_count . saturating_add ( count. to_u64 ( ) . unwrap ( ) ) ;
1449
1472
1450
1473
self . max_index = Some ( index) ;
1451
1474
@@ -1533,6 +1556,7 @@ impl<T: Counter, F: Counter> PartialEq<Histogram<F>> for Histogram<T>
1533
1556
if self . min_nz ( ) != other. min_nz ( ) {
1534
1557
return false ;
1535
1558
}
1559
+ // TODO may panic? Does the above guarantee that the other array is at least as long?
1536
1560
( 0 ..self . len ( ) ) . all ( |i| self [ i] == other[ i] )
1537
1561
}
1538
1562
}
0 commit comments