@@ -85,6 +85,31 @@ pub fn isqrt_u32(n: u32) -> u32 {
8585 }
8686}
8787
88+ /// Floor of `√n` for any `u128`, integer Newton iteration.
89+ ///
90+ /// # Example
91+ ///
92+ /// ```
93+ /// use ndarray::hpc::rolling_floor::isqrt_u128;
94+ /// assert_eq!(isqrt_u128(1 << 64), 1 << 32);
95+ /// assert_eq!(isqrt_u128(u128::MAX), u128::from(u64::MAX));
96+ /// ```
97+ pub fn isqrt_u128 ( n : u128 ) -> u128 {
98+ if n == 0 {
99+ return 0 ;
100+ }
101+ // Start >= floor(√n) so Newton descends monotonically; x ≤ 2^64 and
102+ // n / x ≤ 2^64, so x + n / x cannot overflow.
103+ let mut x = 1u128 << ( ( 129 - n. leading_zeros ( ) ) / 2 ) ;
104+ loop {
105+ let x1 = ( x + n / x) / 2 ;
106+ if x1 >= x {
107+ return x;
108+ }
109+ x = x1;
110+ }
111+ }
112+
88113/// Rank `⌊per_10000 · len / 10000⌋`, clamped to the last index. `0` for an
89114/// empty sample. The integer rank rule for every empirical lookup.
90115///
@@ -227,19 +252,7 @@ impl ReservoirU32 {
227252 if sigma == 0 || self . samples . len ( ) < 4 {
228253 return 300 ;
229254 }
230- let n = self . samples . len ( ) as u128 ;
231- // u128: a fourth power of a u32 difference is below 2^128.
232- let m4: u128 = self
233- . samples
234- . iter ( )
235- . map ( |& d| {
236- let diff = u128:: from ( d. abs_diff ( mu) ) ;
237- diff * diff * diff * diff
238- } )
239- . sum :: < u128 > ( )
240- / n;
241- let s4 = u128:: from ( sigma) . pow ( 4 ) ;
242- u32:: try_from ( m4 * 100 / s4) . unwrap_or ( u32:: MAX )
255+ kurtosis_x100_exact ( & self . samples , mu, sigma) . unwrap_or_else ( || kurtosis_x100_scaled ( & self . samples , mu, sigma) )
243256 }
244257
245258 /// Deterministic splitmix64-style hash that drives replacement.
@@ -352,7 +365,7 @@ impl EmpiricalShape {
352365 let m = moments_u32 ( & sorted) ;
353366 Some ( Self {
354367 mu : saturate_u32 ( m. sum / u128:: from ( m. n ) ) ,
355- sigma : isqrt_u32 ( saturate_u32 ( variance_floor ( & m) ) ) ,
368+ sigma : sqrt_u32 ( variance_floor ( & m) ) ,
356369 sorted,
357370 } )
358371 }
@@ -498,7 +511,7 @@ impl RollingFloor {
498511 assert ! ( sample. len( ) > 1 , "need at least 2 samples to calibrate" ) ;
499512 let moments = moments_u32 ( sample) ;
500513 let mu = saturate_u32 ( moments. sum / u128:: from ( moments. n ) ) ;
501- let sigma = isqrt_u32 ( saturate_u32 ( centred_on_floor_mean ( & moments) / u128:: from ( moments. n ) ) ) . max ( 1 ) ;
514+ let sigma = sqrt_u32 ( centred_on_floor_mean ( & moments) / u128:: from ( moments. n ) ) . max ( 1 ) ;
502515 let mut floor = Self :: from_params_and_moments ( mu, sigma, moments) ;
503516 for & d in sample {
504517 floor. reservoir . observe ( d) ;
@@ -564,7 +577,7 @@ impl RollingFloor {
564577 /// The periodic path: drift first; the shape only when there is none.
565578 fn checkpoint ( & mut self ) -> Option < FloorShift > {
566579 let run_mu = saturate_u32 ( self . moments . sum / u128:: from ( self . moments . n ) ) ;
567- let run_sigma = isqrt_u32 ( saturate_u32 ( variance_floor ( & self . moments ) ) ) . max ( 1 ) ;
580+ let run_sigma = sqrt_u32 ( variance_floor ( & self . moments ) ) . max ( 1 ) ;
568581
569582 let mu_drift = run_mu. abs_diff ( self . anchor_mu ) ;
570583 let sigma_drift = run_sigma. abs_diff ( self . anchor_sigma ) ;
@@ -606,10 +619,7 @@ impl RollingFloor {
606619 if self . moments . n == 0 {
607620 return None ;
608621 }
609- Some ( (
610- saturate_u32 ( self . moments . sum / u128:: from ( self . moments . n ) ) ,
611- isqrt_u32 ( saturate_u32 ( variance_floor ( & self . moments ) ) ) ,
612- ) )
622+ Some ( ( saturate_u32 ( self . moments . sum / u128:: from ( self . moments . n ) ) , sqrt_u32 ( variance_floor ( & self . moments ) ) ) )
613623 }
614624
615625 /// The coordinates a threshold is located in: the running ones, or the
@@ -621,7 +631,9 @@ impl RollingFloor {
621631
622632 fn locate ( & self , level : SigmaLevel , mu : u32 , sigma : u32 ) -> u32 {
623633 match & self . shape {
624- Shape :: Gaussian => mu. saturating_sub ( level. quarters ( ) * sigma / 4 ) ,
634+ Shape :: Gaussian => {
635+ saturate_u32 ( u128:: from ( mu) . saturating_sub ( u128:: from ( level. quarters ( ) ) * u128:: from ( sigma) / 4 ) )
636+ }
625637 Shape :: Empirical ( e) => e. locate ( level, mu, sigma) ,
626638 }
627639 }
@@ -692,6 +704,43 @@ impl RollingFloor {
692704 }
693705}
694706
707+ /// `⌊100 · E[(X − μ)⁴] / σ⁴⌋` exactly, or `None` when an intermediate
708+ /// leaves `u128` (only for spreads far beyond any popcount width).
709+ fn kurtosis_x100_exact ( samples : & [ u32 ] , mu : u32 , sigma : u32 ) -> Option < u32 > {
710+ let mut sum: u128 = 0 ;
711+ for & d in samples {
712+ let diff = u128:: from ( d. abs_diff ( mu) ) ;
713+ sum = sum. checked_add ( ( diff * diff) . checked_mul ( diff * diff) ?) ?;
714+ }
715+ let m4 = sum / samples. len ( ) as u128 ;
716+ Some ( u32:: try_from ( m4. checked_mul ( 100 ) ? / u128:: from ( sigma) . pow ( 4 ) ) . unwrap_or ( u32:: MAX ) )
717+ }
718+
719+ /// The same ratio from per-sample `(d/σ)²` in 16.16 fixed point, used only
720+ /// when the exact form overflows. Where it would overflow too the kurtosis is
721+ /// astronomically large and saturates at `u32::MAX`. Agrees with the exact
722+ /// form to within one unit where both fit.
723+ fn kurtosis_x100_scaled ( samples : & [ u32 ] , mu : u32 , sigma : u32 ) -> u32 {
724+ let s2 = u128:: from ( sigma) * u128:: from ( sigma) ;
725+ let mut sum: u128 = 0 ;
726+ for & d in samples {
727+ let diff = u128:: from ( d. abs_diff ( mu) ) ;
728+ let r = ( ( diff * diff) << 16 ) / s2; // (d/σ)², 16 fractional bits
729+ match r. checked_mul ( r) . and_then ( |t| sum. checked_add ( t) ) {
730+ Some ( v) => sum = v,
731+ None => return u32:: MAX ,
732+ }
733+ }
734+ let m4 = sum / samples. len ( ) as u128 ; // 32 fractional bits
735+ u32:: try_from ( m4. saturating_mul ( 100 ) >> 32 ) . unwrap_or ( u32:: MAX )
736+ }
737+
738+ /// `⌊√n⌋` for a variance of `u32` values. That variance is below `2^62`, so
739+ /// the root fits `u32`.
740+ fn sqrt_u32 ( n : u128 ) -> u32 {
741+ saturate_u32 ( isqrt_u128 ( n) )
742+ }
743+
695744fn saturate_u32 ( x : u128 ) -> u32 {
696745 u32:: try_from ( x) . unwrap_or ( u32:: MAX )
697746}
@@ -919,6 +968,52 @@ mod tests {
919968 }
920969 }
921970
971+ /// The full `u32` range: σ above 65 535, `k·σ` above `u32`, fourth-power
972+ /// sums above `u128` — none may clamp, wrap or panic.
973+ #[ test]
974+ fn full_u32_range_does_not_clamp_or_overflow ( ) {
975+ const Q : u32 = u32:: MAX / 2 ; // 2 147 483 647
976+ // Variance of {0, MAX} is Q² + Q (floor); its root is Q, not 65 535.
977+ let f = RollingFloor :: calibrate ( & [ 0 , u32:: MAX ] ) ;
978+ assert_eq ! ( ( f. mu( ) , f. sigma( ) ) , ( Q , Q ) ) ;
979+ assert_eq ! ( f. coordinates( ) , Some ( ( Q , Q ) ) ) ;
980+ let e = EmpiricalShape :: from_sample ( & [ 0 , u32:: MAX ] ) . unwrap ( ) ;
981+ assert_eq ! ( ( e. mu( ) , e. sigma( ) ) , ( Q , Q ) ) ;
982+
983+ // k·σ is formed wide: 4 · 1.5e9 does not fit u32.
984+ let g = RollingFloor :: from_params ( u32:: MAX , 1_500_000_000 ) ;
985+ assert_eq ! ( g. threshold( SigmaLevel ( 4 ) ) , u32 :: MAX - 1_500_000_000 ) ;
986+ assert_eq ! ( g. threshold( SigmaLevel ( 12 ) ) , 0 , "saturates, never wraps" ) ;
987+
988+ // A two-point distribution has kurtosis exactly 1, i.e. 100.
989+ let mut r = ReservoirU32 :: new ( 1000 ) ;
990+ ( 0 ..1000u32 ) . for_each ( |i| r. observe ( if i % 2 == 0 { 0 } else { u32:: MAX } ) ) ;
991+ assert ! ( ( 99 ..=101 ) . contains( & r. kurtosis( Q , Q ) ) , "{}" , r. kurtosis( Q , Q ) ) ;
992+ }
993+
994+ /// Where the exact kurtosis fits, the overflow-safe path agrees with it
995+ /// to within one unit of the ×100 scale.
996+ #[ test]
997+ fn kurtosis_fallback_matches_the_exact_path ( ) {
998+ for ( mu, sigma, seed) in [ ( 8192 , 64 , 1 ) , ( 5000 , 3 , 2 ) , ( 100_000 , 900 , 3 ) ] {
999+ let xs = normalish ( 1000 , mu, sigma, seed) ;
1000+ let mut r = ReservoirU32 :: new ( 1000 ) ;
1001+ xs. iter ( ) . for_each ( |& d| r. observe ( d) ) ;
1002+ let exact = kurtosis_x100_exact ( r. samples ( ) , mu, sigma) . expect ( "fits" ) ;
1003+ let scaled = kurtosis_x100_scaled ( r. samples ( ) , mu, sigma) ;
1004+ assert ! ( exact. abs_diff( scaled) <= 1 , "exact {exact} scaled {scaled}" ) ;
1005+ }
1006+ }
1007+
1008+ #[ test]
1009+ fn isqrt_u128_is_floor_sqrt ( ) {
1010+ for n in ( 0 ..100_000u128 ) . chain ( [ u128:: MAX , u128:: MAX - 1 , ( 1 << 64 ) - 1 , 1 << 64 , ( 1 << 126 ) + 12345 ] ) {
1011+ let r = isqrt_u128 ( n) ;
1012+ assert ! ( r. checked_mul( r) . is_some_and( |sq| sq <= n) , "n {n}" ) ;
1013+ assert ! ( ( r + 1 ) . checked_mul( r + 1 ) . is_none_or( |sq| sq > n) , "n {n}" ) ;
1014+ }
1015+ }
1016+
9221017 #[ test]
9231018 fn scalar_equals_singleton_batch ( ) {
9241019 let xs = stream ( 5000 , 8000 , 300 , 11 ) ;
@@ -1298,7 +1393,7 @@ mod tests {
12981393 m. observe ( d) ;
12991394 if let Some ( ( lmu, lsig) ) = legacy. observe ( d) {
13001395 let emu = saturate_u32 ( m. sum / u128:: from ( m. n ) ) ;
1301- let esig = isqrt_u32 ( saturate_u32 ( variance_floor ( & m) ) ) . max ( 1 ) ;
1396+ let esig = sqrt_u32 ( variance_floor ( & m) ) . max ( 1 ) ;
13021397 assert_eq ! ( ( emu, esig) , ( lmu, lsig) , "mu {mu} sigma {sigma} n {}" , m. n) ;
13031398 checked += 1 ;
13041399 }
0 commit comments