chie_shared/utils/
calculations.rs

1//! Mathematical calculation utility functions.
2
3use crate::{Bytes, Points};
4
5/// Calculate percentage with proper rounding.
6///
7/// # Examples
8///
9/// ```
10/// use chie_shared::calculate_percentage;
11///
12/// // Calculate what percentage 25 is of 100
13/// let percentage = calculate_percentage(25, 100);
14/// assert_eq!(percentage, 25.0);
15///
16/// // Calculate what percentage 1 is of 3
17/// let percentage = calculate_percentage(1, 3);
18/// assert!((percentage - 33.333).abs() < 0.01);
19///
20/// // Handle division by zero
21/// let percentage = calculate_percentage(10, 0);
22/// assert_eq!(percentage, 0.0);
23/// ```
24#[inline]
25#[must_use]
26pub fn calculate_percentage(part: u64, total: u64) -> f64 {
27    if total == 0 {
28        return 0.0;
29    }
30    (part as f64 / total as f64) * 100.0
31}
32
33/// Calculate bandwidth in Mbps from bytes and duration.
34///
35/// # Examples
36///
37/// ```
38/// use chie_shared::calculate_bandwidth_mbps;
39///
40/// // 1 MB transferred in 1 second = 8 Mbps
41/// let bandwidth = calculate_bandwidth_mbps(1_000_000, 1000);
42/// assert!((bandwidth - 8.0).abs() < 0.01);
43///
44/// // 10 MB in 2 seconds = 40 Mbps
45/// let bandwidth = calculate_bandwidth_mbps(10_000_000, 2000);
46/// assert!((bandwidth - 40.0).abs() < 0.01);
47///
48/// // Handle zero duration
49/// let bandwidth = calculate_bandwidth_mbps(1_000_000, 0);
50/// assert_eq!(bandwidth, 0.0);
51/// ```
52#[inline]
53#[must_use]
54pub fn calculate_bandwidth_mbps(bytes: Bytes, duration_ms: u64) -> f64 {
55    if duration_ms == 0 {
56        return 0.0;
57    }
58
59    let bits = (bytes * 8) as f64;
60    let seconds = duration_ms as f64 / 1000.0;
61    (bits / seconds) / 1_000_000.0
62}
63
64/// Calculate latency from start and end timestamps.
65#[inline]
66pub fn calculate_latency_ms(start_ms: i64, end_ms: i64) -> u32 {
67    (end_ms - start_ms).max(0) as u32
68}
69
70/// Calculate estimated transfer time in seconds.
71#[inline]
72#[must_use]
73pub fn estimate_transfer_time(bytes: Bytes, bandwidth_bps: u64) -> u64 {
74    if bandwidth_bps == 0 {
75        return u64::MAX;
76    }
77
78    let bits = bytes * 8;
79    bits / bandwidth_bps
80}
81
82/// Calculate reward multiplier based on demand/supply ratio.
83///
84/// Returns a multiplier between 1.0x and 3.0x based on the demand/supply ratio.
85/// - Low demand (ratio ≤ 0.5): 1.0x multiplier
86/// - High demand (ratio ≥ 2.0): 3.0x multiplier
87/// - Medium demand: Linear interpolation between 1.0x and 3.0x
88///
89/// # Examples
90///
91/// ```
92/// use chie_shared::calculate_demand_multiplier;
93///
94/// // Low demand: plenty of supply
95/// let multiplier = calculate_demand_multiplier(50, 100);
96/// assert_eq!(multiplier, 1.0);
97///
98/// // High demand: scarce supply
99/// let multiplier = calculate_demand_multiplier(200, 100);
100/// assert_eq!(multiplier, 3.0);
101///
102/// // Medium demand: 1:1 ratio
103/// let multiplier = calculate_demand_multiplier(100, 100);
104/// assert!((multiplier - 1.666).abs() < 0.01);
105///
106/// // No supply: maximum multiplier
107/// let multiplier = calculate_demand_multiplier(100, 0);
108/// assert_eq!(multiplier, 3.0);
109/// ```
110#[inline]
111#[must_use]
112pub fn calculate_demand_multiplier(demand: u64, supply: u64) -> f64 {
113    if supply == 0 {
114        return 3.0; // Maximum multiplier
115    }
116
117    let ratio = demand as f64 / supply as f64;
118
119    // Clamp between 1.0x and 3.0x
120    if ratio <= 0.5 {
121        1.0
122    } else if ratio >= 2.0 {
123        3.0
124    } else {
125        1.0 + (ratio - 0.5) * (2.0 / 1.5)
126    }
127}
128
129/// Calculate z-score for anomaly detection.
130///
131/// The z-score indicates how many standard deviations away from the mean a value is.
132/// Values with |z-score| > 3 are typically considered anomalies.
133///
134/// # Examples
135///
136/// ```
137/// use chie_shared::calculate_z_score;
138///
139/// // Value at the mean has z-score of 0
140/// let z = calculate_z_score(100.0, 100.0, 10.0);
141/// assert_eq!(z, 0.0);
142///
143/// // Value 1 std dev above mean has z-score of 1
144/// let z = calculate_z_score(110.0, 100.0, 10.0);
145/// assert_eq!(z, 1.0);
146///
147/// // Value 2 std devs below mean has z-score of -2
148/// let z = calculate_z_score(80.0, 100.0, 10.0);
149/// assert_eq!(z, -2.0);
150///
151/// // Anomaly: value is 3.5 std devs above mean
152/// let z = calculate_z_score(135.0, 100.0, 10.0);
153/// assert_eq!(z, 3.5);
154/// assert!(z.abs() > 3.0); // Likely an anomaly
155///
156/// // Handle zero std dev
157/// let z = calculate_z_score(100.0, 100.0, 0.0);
158/// assert_eq!(z, 0.0);
159/// ```
160#[inline]
161#[must_use]
162pub fn calculate_z_score(value: f64, mean: f64, std_dev: f64) -> f64 {
163    if std_dev == 0.0 {
164        return 0.0;
165    }
166
167    (value - mean) / std_dev
168}
169
170/// Calculate storage cost per month (points per GB).
171#[inline]
172#[must_use]
173pub fn calculate_storage_cost(size_bytes: Bytes, rate_per_gb_month: Points) -> Points {
174    let gb = (size_bytes as f64) / (1024.0 * 1024.0 * 1024.0);
175    (gb * rate_per_gb_month as f64).ceil() as Points
176}
177
178/// Convert bytes to gigabytes with precision (f64 version for non-const contexts).
179#[inline]
180pub fn bytes_to_gb_f64(bytes: Bytes) -> f64 {
181    bytes as f64 / (1024.0 * 1024.0 * 1024.0)
182}
183
184/// Convert gigabytes to bytes (f64 version for non-const contexts).
185#[inline]
186pub fn gb_to_bytes_f64(gb: f64) -> Bytes {
187    (gb * 1024.0 * 1024.0 * 1024.0) as Bytes
188}
189
190/// Calculate reward with multiplier and penalty based on latency.
191#[inline]
192#[must_use]
193pub fn calculate_reward_with_penalty(
194    base_reward: Points,
195    multiplier: f64,
196    latency_ms: u32,
197    latency_threshold_ms: u32,
198) -> Points {
199    let reward_with_multiplier = (base_reward as f64 * multiplier) as Points;
200
201    if latency_ms > latency_threshold_ms {
202        // Apply 50% penalty for high latency
203        reward_with_multiplier / 2
204    } else {
205        reward_with_multiplier
206    }
207}
208
209/// Calculate exponential moving average.
210#[inline]
211#[must_use]
212pub fn calculate_ema(current: f64, new_value: f64, alpha: f64) -> f64 {
213    alpha * new_value + (1.0 - alpha) * current
214}
215
216/// Calculate standard deviation from a slice of values.
217#[inline]
218#[must_use]
219pub fn calculate_std_dev(values: &[f64]) -> f64 {
220    if values.is_empty() {
221        return 0.0;
222    }
223
224    let mean = values.iter().sum::<f64>() / values.len() as f64;
225    let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
226    variance.sqrt()
227}
228
229/// Calculate mean of values.
230#[inline]
231#[must_use]
232pub fn calculate_mean(values: &[f64]) -> f64 {
233    if values.is_empty() {
234        return 0.0;
235    }
236    values.iter().sum::<f64>() / values.len() as f64
237}
238
239/// Calculate median of a slice of values.
240pub fn calculate_median(values: &[f64]) -> f64 {
241    if values.is_empty() {
242        return 0.0;
243    }
244
245    let mut sorted = values.to_vec();
246    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
247
248    let mid = sorted.len() / 2;
249    if sorted.len() % 2 == 0 {
250        (sorted[mid - 1] + sorted[mid]) / 2.0
251    } else {
252        sorted[mid]
253    }
254}
255
256/// Calculate percentile of a slice of values (p should be between 0.0 and 1.0).
257pub fn calculate_percentile(values: &[f64], p: f64) -> f64 {
258    if values.is_empty() {
259        return 0.0;
260    }
261
262    let p = p.clamp(0.0, 1.0);
263    let mut sorted = values.to_vec();
264    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
265
266    let index = (p * (sorted.len() - 1) as f64).round() as usize;
267    sorted[index]
268}
269
270/// Calculate min, max, and average from a slice of values.
271pub fn calculate_stats(values: &[f64]) -> (f64, f64, f64) {
272    if values.is_empty() {
273        return (0.0, 0.0, 0.0);
274    }
275
276    let min = values
277        .iter()
278        .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
279        .copied()
280        .unwrap_or(0.0);
281
282    let max = values
283        .iter()
284        .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
285        .copied()
286        .unwrap_or(0.0);
287
288    let avg = calculate_mean(values);
289
290    (min, max, avg)
291}
292
293/// Check if value is an outlier using IQR method.
294pub fn is_outlier_iqr(values: &[f64], value: f64) -> bool {
295    if values.len() < 4 {
296        return false;
297    }
298
299    let q1 = calculate_percentile(values, 0.25);
300    let q3 = calculate_percentile(values, 0.75);
301    let iqr = q3 - q1;
302
303    let lower_bound = q1 - 1.5 * iqr;
304    let upper_bound = q3 + 1.5 * iqr;
305
306    value < lower_bound || value > upper_bound
307}
308
309/// Calculate moving average over a window.
310pub fn calculate_moving_average(values: &[f64], window_size: usize) -> Vec<f64> {
311    if values.is_empty() || window_size == 0 {
312        return Vec::new();
313    }
314
315    let mut result = Vec::new();
316    for i in 0..values.len() {
317        let start = if i >= window_size {
318            i - window_size + 1
319        } else {
320            0
321        };
322        let window = &values[start..=i];
323        result.push(calculate_mean(window));
324    }
325
326    result
327}
328
329/// Round up to the nearest multiple of n.
330pub fn round_up_to_multiple(value: u64, n: u64) -> u64 {
331    if n == 0 {
332        return value;
333    }
334    value.div_ceil(n) * n
335}
336
337/// Round down to the nearest multiple of n.
338pub fn round_down_to_multiple(value: u64, n: u64) -> u64 {
339    if n == 0 {
340        return value;
341    }
342    (value / n) * n
343}
344
345/// Calculate compound growth rate.
346pub fn calculate_growth_rate(initial: f64, final_val: f64, periods: u32) -> f64 {
347    if initial == 0.0 || periods == 0 {
348        return 0.0;
349    }
350    ((final_val / initial).powf(1.0 / periods as f64) - 1.0) * 100.0
351}
352
353/// Calculate reputation score with decay over time.
354pub fn calculate_reputation_decay(
355    current_reputation: f32,
356    days_elapsed: f32,
357    decay_rate: f32,
358) -> f32 {
359    let decayed = current_reputation - (current_reputation * decay_rate * days_elapsed);
360    decayed.clamp(crate::MIN_REPUTATION, crate::MAX_REPUTATION)
361}
362
363/// Update reputation based on success/failure events.
364pub fn update_reputation(current: f32, success: bool, weight: f32) -> f32 {
365    let delta = if success { weight } else { -weight * 2.0 }; // Failures have 2x impact
366    (current + delta).clamp(crate::MIN_REPUTATION, crate::MAX_REPUTATION)
367}
368
369/// Calculate reputation boost for consistent good behavior.
370pub fn calculate_reputation_bonus(consecutive_successes: u32) -> f32 {
371    // Bonus caps at 20 consecutive successes
372    let capped_successes = consecutive_successes.min(20);
373    (capped_successes as f32).sqrt() * 0.5 // Up to ~2.2 bonus points
374}
375
376/// Calculate tokens available in a token bucket.
377pub fn calculate_token_bucket(
378    current_tokens: f64,
379    capacity: f64,
380    refill_rate: f64,
381    time_elapsed_secs: f64,
382) -> f64 {
383    let new_tokens = current_tokens + (refill_rate * time_elapsed_secs);
384    new_tokens.min(capacity)
385}
386
387/// Check if an action is allowed under rate limiting (token bucket).
388pub fn is_rate_limit_allowed(current_tokens: f64, cost: f64) -> bool {
389    current_tokens >= cost
390}
391
392/// Calculate sliding window rate limit.
393pub fn calculate_sliding_window_count(
394    timestamps: &[i64],
395    window_start_ms: i64,
396    window_end_ms: i64,
397) -> usize {
398    timestamps
399        .iter()
400        .filter(|&&ts| ts >= window_start_ms && ts <= window_end_ms)
401        .count()
402}
403
404/// Calculate platform fee from total points.
405pub fn calculate_platform_fee(total_points: Points, fee_percentage: f64) -> Points {
406    (total_points as f64 * fee_percentage) as Points
407}
408
409/// Calculate creator share from total points.
410pub fn calculate_creator_share(total_points: Points, share_percentage: f64) -> Points {
411    (total_points as f64 * share_percentage) as Points
412}
413
414/// Calculate provider earnings after fees and creator share.
415pub fn calculate_provider_earnings(
416    total_points: Points,
417    platform_fee_pct: f64,
418    creator_share_pct: f64,
419) -> Points {
420    let platform_fee = calculate_platform_fee(total_points, platform_fee_pct);
421    let creator_share = calculate_creator_share(total_points, creator_share_pct);
422    total_points
423        .saturating_sub(platform_fee)
424        .saturating_sub(creator_share)
425}
426
427/// Calculate content pricing based on size and demand.
428pub fn calculate_content_price(
429    size_bytes: Bytes,
430    base_price_per_gb: Points,
431    demand_multiplier: f64,
432) -> Points {
433    let gb = bytes_to_gb_f64(size_bytes);
434    ((gb * base_price_per_gb as f64) * demand_multiplier).ceil() as Points
435}
436
437/// Clamp a value between a minimum and maximum.
438///
439/// # Examples
440///
441/// ```
442/// use chie_shared::clamp;
443///
444/// assert_eq!(clamp(5, 0, 10), 5);
445/// assert_eq!(clamp(-5, 0, 10), 0);  // Below min
446/// assert_eq!(clamp(15, 0, 10), 10); // Above max
447///
448/// // Works with floats too
449/// assert_eq!(clamp(3.5, 0.0, 5.0), 3.5);
450/// assert_eq!(clamp(-1.0, 0.0, 5.0), 0.0);
451/// ```
452pub fn clamp<T: PartialOrd>(value: T, min: T, max: T) -> T {
453    if value < min {
454        min
455    } else if value > max {
456        max
457    } else {
458        value
459    }
460}
461
462/// Linear interpolation between two values.
463///
464/// # Examples
465///
466/// ```
467/// use chie_shared::lerp;
468///
469/// // Interpolate between 0 and 100
470/// assert_eq!(lerp(0.0, 100.0, 0.0), 0.0);   // t=0 returns start
471/// assert_eq!(lerp(0.0, 100.0, 1.0), 100.0); // t=1 returns end
472/// assert_eq!(lerp(0.0, 100.0, 0.5), 50.0);  // t=0.5 returns midpoint
473///
474/// // Can extrapolate with t > 1 or t < 0
475/// assert_eq!(lerp(0.0, 100.0, 2.0), 200.0);
476/// assert_eq!(lerp(0.0, 100.0, -0.5), -50.0);
477/// ```
478#[inline]
479pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
480    a + (b - a) * t
481}
482
483/// Calculate percentage change between two values.
484/// Returns positive for increase, negative for decrease.
485/// Returns 0.0 if old_value is 0.
486pub fn calculate_percentage_change(old_value: f64, new_value: f64) -> f64 {
487    if old_value == 0.0 {
488        if new_value == 0.0 {
489            0.0
490        } else {
491            100.0 // or f64::INFINITY, but 100% is more practical
492        }
493    } else {
494        ((new_value - old_value) / old_value) * 100.0
495    }
496}
497
498/// Calculate rate (events per time unit).
499/// Returns events per second.
500pub fn calculate_rate(event_count: u64, duration_ms: u64) -> f64 {
501    if duration_ms == 0 {
502        return 0.0;
503    }
504
505    (event_count as f64 / duration_ms as f64) * 1000.0
506}
507
508/// Normalize a value to 0.0-1.0 range based on min and max bounds.
509/// Values outside bounds are clamped to 0.0 or 1.0.
510///
511/// # Examples
512///
513/// ```
514/// use chie_shared::normalize;
515///
516/// // Normalize values in range 0-100
517/// assert_eq!(normalize(0.0, 0.0, 100.0), 0.0);
518/// assert_eq!(normalize(100.0, 0.0, 100.0), 1.0);
519/// assert_eq!(normalize(50.0, 0.0, 100.0), 0.5);
520///
521/// // Values outside range are clamped
522/// assert_eq!(normalize(-10.0, 0.0, 100.0), 0.0);
523/// assert_eq!(normalize(150.0, 0.0, 100.0), 1.0);
524///
525/// // Special case: when min == max, returns 0.5
526/// assert_eq!(normalize(5.0, 5.0, 5.0), 0.5);
527/// ```
528pub fn normalize(value: f64, min: f64, max: f64) -> f64 {
529    if max == min {
530        return 0.5; // Arbitrary choice when range is zero
531    }
532
533    let normalized = (value - min) / (max - min);
534    normalized.clamp(0.0, 1.0)
535}
536
537/// Calculate the average of two values.
538pub fn average(a: f64, b: f64) -> f64 {
539    (a + b) / 2.0
540}
541
542/// Check if a value is within a tolerance of a target.
543pub fn is_within_tolerance(value: f64, target: f64, tolerance: f64) -> bool {
544    (value - target).abs() <= tolerance
545}
546
547/// Convert bits per second to megabits per second.
548pub fn bps_to_mbps(bps: u64) -> f64 {
549    bps as f64 / 1_000_000.0
550}
551
552/// Convert megabits per second to bits per second.
553pub fn mbps_to_bps(mbps: f64) -> u64 {
554    (mbps * 1_000_000.0) as u64
555}
556
557/// Calculate uptime percentage from total time and downtime.
558pub fn calculate_uptime_percentage(total_ms: u64, downtime_ms: u64) -> f64 {
559    if total_ms == 0 {
560        return 100.0;
561    }
562
563    let uptime_ms = total_ms.saturating_sub(downtime_ms);
564    (uptime_ms as f64 / total_ms as f64) * 100.0
565}
566
567/// Calculate the byte offset for a chunk index.
568/// Returns the starting byte position in the content for the given chunk.
569#[inline]
570#[must_use]
571pub const fn chunk_offset(chunk_index: u64, chunk_size: u64) -> u64 {
572    chunk_index * chunk_size
573}
574
575/// Calculate which chunk contains a given byte offset.
576/// Returns the chunk index that contains the specified byte.
577#[inline]
578#[must_use]
579pub const fn byte_to_chunk_index(byte_offset: u64, chunk_size: u64) -> u64 {
580    if chunk_size == 0 {
581        0
582    } else {
583        byte_offset / chunk_size
584    }
585}
586
587/// Get the byte range (start, end) for a specific chunk.
588/// Returns (start_byte, end_byte_exclusive) for the chunk.
589#[inline]
590#[must_use]
591pub const fn chunk_byte_range(chunk_index: u64, chunk_size: u64, total_size: u64) -> (u64, u64) {
592    let start = chunk_index * chunk_size;
593    let end = if start + chunk_size > total_size {
594        total_size
595    } else {
596        start + chunk_size
597    };
598    (start, end)
599}
600
601/// Check if a chunk index is valid for the given content size.
602#[inline]
603#[must_use]
604pub const fn is_valid_chunk_index(chunk_index: u64, total_size: u64, chunk_size: u64) -> bool {
605    if chunk_size == 0 {
606        return false;
607    }
608    let max_chunks = total_size.div_ceil(chunk_size);
609    chunk_index < max_chunks
610}
611
612/// Calculate the actual size of a specific chunk (last chunk may be smaller).
613#[inline]
614#[must_use]
615pub const fn actual_chunk_size(chunk_index: u64, chunk_size: u64, total_size: u64) -> u64 {
616    let start = chunk_index * chunk_size;
617    if start >= total_size {
618        0
619    } else {
620        let remaining = total_size - start;
621        if remaining < chunk_size {
622            remaining
623        } else {
624            chunk_size
625        }
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632
633    #[test]
634    fn test_calculate_percentage() {
635        assert_eq!(calculate_percentage(50, 100), 50.0);
636        assert_eq!(calculate_percentage(0, 100), 0.0);
637        assert_eq!(calculate_percentage(100, 100), 100.0);
638        assert_eq!(calculate_percentage(25, 0), 0.0);
639    }
640
641    #[test]
642    fn test_calculate_bandwidth_mbps() {
643        // 1 MB in 1 second = 8 Mbps
644        assert_eq!(calculate_bandwidth_mbps(1_048_576, 1000), 8.388_608);
645        assert_eq!(calculate_bandwidth_mbps(0, 1000), 0.0);
646        assert_eq!(calculate_bandwidth_mbps(1000, 0), 0.0);
647    }
648
649    #[test]
650    fn test_calculate_latency() {
651        assert_eq!(calculate_latency_ms(1000, 1500), 500);
652        assert_eq!(calculate_latency_ms(1500, 1000), 0);
653    }
654
655    #[test]
656    fn test_estimate_transfer_time() {
657        // 1 MB at 1 Mbps = 8 seconds
658        assert_eq!(estimate_transfer_time(1_048_576, 1_000_000), 8);
659        assert_eq!(estimate_transfer_time(1000, 0), u64::MAX);
660    }
661
662    #[test]
663    fn test_calculate_demand_multiplier() {
664        // Low demand (ratio <= 0.5): 1.0x multiplier
665        assert_eq!(calculate_demand_multiplier(1, 4), 1.0);
666        // Medium demand (ratio = 1.0): ~1.67x multiplier
667        assert!((calculate_demand_multiplier(2, 2) - 1.666_666_666_666_667).abs() < 0.001);
668        // High demand (ratio >= 2.0): 3.0x multiplier
669        assert_eq!(calculate_demand_multiplier(4, 2), 3.0);
670        // No supply: maximum 3.0x multiplier
671        assert_eq!(calculate_demand_multiplier(10, 0), 3.0);
672    }
673
674    #[test]
675    fn test_calculate_z_score() {
676        assert_eq!(calculate_z_score(100.0, 100.0, 10.0), 0.0);
677        assert_eq!(calculate_z_score(110.0, 100.0, 10.0), 1.0);
678        assert_eq!(calculate_z_score(90.0, 100.0, 10.0), -1.0);
679        assert_eq!(calculate_z_score(100.0, 100.0, 0.0), 0.0);
680    }
681
682    #[test]
683    fn test_calculate_storage_cost() {
684        // 1 GB at 10 points/GB/month = 10 points
685        assert_eq!(calculate_storage_cost(1_073_741_824, 10), 10);
686        // 500 MB at 10 points/GB/month = 5 points (rounded up)
687        assert_eq!(calculate_storage_cost(536_870_912, 10), 5);
688    }
689
690    #[test]
691    fn test_bytes_to_gb_f64() {
692        assert_eq!(bytes_to_gb_f64(1_073_741_824), 1.0);
693        assert_eq!(bytes_to_gb_f64(536_870_912), 0.5);
694    }
695
696    #[test]
697    fn test_gb_to_bytes_f64() {
698        assert_eq!(gb_to_bytes_f64(1.0), 1_073_741_824);
699        assert_eq!(gb_to_bytes_f64(0.5), 536_870_912);
700    }
701
702    #[test]
703    fn test_calculate_reward_with_penalty() {
704        // No penalty if latency is within threshold
705        assert_eq!(calculate_reward_with_penalty(100, 2.0, 400, 500), 200);
706        // 50% penalty if latency exceeds threshold
707        assert_eq!(calculate_reward_with_penalty(100, 2.0, 600, 500), 100);
708    }
709
710    #[test]
711    fn test_calculate_ema() {
712        let current = 100.0;
713        let new_value = 120.0;
714        let alpha = 0.3;
715        let expected = alpha * new_value + (1.0 - alpha) * current;
716        assert_eq!(calculate_ema(current, new_value, alpha), expected);
717    }
718
719    #[test]
720    fn test_calculate_std_dev() {
721        let values = vec![10.0, 12.0, 23.0, 23.0, 16.0, 23.0, 21.0, 16.0];
722        let std_dev = calculate_std_dev(&values);
723        assert!((std_dev - 4.898_979_485_566_356).abs() < 0.001);
724
725        assert_eq!(calculate_std_dev(&[]), 0.0);
726    }
727
728    #[test]
729    fn test_calculate_mean() {
730        assert_eq!(calculate_mean(&[1.0, 2.0, 3.0, 4.0, 5.0]), 3.0);
731        assert_eq!(calculate_mean(&[]), 0.0);
732    }
733
734    #[test]
735    fn test_calculate_median() {
736        assert_eq!(calculate_median(&[1.0, 2.0, 3.0, 4.0, 5.0]), 3.0);
737        assert_eq!(calculate_median(&[1.0, 2.0, 3.0, 4.0]), 2.5);
738        assert_eq!(calculate_median(&[5.0]), 5.0);
739        assert_eq!(calculate_median(&[]), 0.0);
740    }
741
742    #[test]
743    fn test_calculate_percentile() {
744        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
745        assert_eq!(calculate_percentile(&values, 0.0), 1.0);
746        assert_eq!(calculate_percentile(&values, 0.5), 6.0); // 50th percentile of 10 values is at index 5 (6.0)
747        assert_eq!(calculate_percentile(&values, 1.0), 10.0);
748    }
749
750    #[test]
751    fn test_calculate_stats() {
752        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
753        let (min, max, avg) = calculate_stats(&values);
754        assert_eq!(min, 1.0);
755        assert_eq!(max, 5.0);
756        assert_eq!(avg, 3.0);
757    }
758
759    #[test]
760    fn test_is_outlier_iqr() {
761        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
762        assert!(!is_outlier_iqr(&values, 5.0));
763        assert!(is_outlier_iqr(&values, 100.0));
764        assert!(is_outlier_iqr(&values, -100.0));
765    }
766
767    #[test]
768    fn test_calculate_moving_average() {
769        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
770        let ma = calculate_moving_average(&values, 3);
771        assert_eq!(ma.len(), 5);
772        assert_eq!(ma[0], 1.0); // avg of [1.0]
773        assert_eq!(ma[1], 1.5); // avg of [1.0, 2.0]
774        assert_eq!(ma[2], 2.0); // avg of [1.0, 2.0, 3.0]
775        assert_eq!(ma[3], 3.0); // avg of [2.0, 3.0, 4.0]
776        assert_eq!(ma[4], 4.0); // avg of [3.0, 4.0, 5.0]
777    }
778
779    #[test]
780    fn test_round_up_to_multiple() {
781        assert_eq!(round_up_to_multiple(10, 5), 10);
782        assert_eq!(round_up_to_multiple(11, 5), 15);
783        assert_eq!(round_up_to_multiple(14, 5), 15);
784        assert_eq!(round_up_to_multiple(15, 5), 15);
785    }
786
787    #[test]
788    fn test_round_down_to_multiple() {
789        assert_eq!(round_down_to_multiple(10, 5), 10);
790        assert_eq!(round_down_to_multiple(11, 5), 10);
791        assert_eq!(round_down_to_multiple(14, 5), 10);
792        assert_eq!(round_down_to_multiple(15, 5), 15);
793    }
794
795    #[test]
796    fn test_calculate_growth_rate() {
797        let rate = calculate_growth_rate(100.0, 121.0, 2);
798        assert!((rate - 10.0).abs() < 0.01); // ~10% growth rate
799    }
800
801    #[test]
802    fn test_calculate_reputation_decay() {
803        let reputation = calculate_reputation_decay(100.0, 10.0, 0.01);
804        assert_eq!(reputation, 90.0);
805
806        // Should not go below MIN_REPUTATION
807        let decayed = calculate_reputation_decay(10.0, 1000.0, 0.01);
808        assert_eq!(decayed, crate::MIN_REPUTATION);
809
810        // Should not go above MAX_REPUTATION
811        let no_decay = calculate_reputation_decay(100.0, 0.0, 0.01);
812        assert_eq!(no_decay, 100.0);
813    }
814
815    #[test]
816    fn test_update_reputation() {
817        // Success increases reputation
818        let updated = update_reputation(50.0, true, 1.0);
819        assert_eq!(updated, 51.0);
820
821        // Failure decreases reputation (2x impact)
822        let updated = update_reputation(50.0, false, 1.0);
823        assert_eq!(updated, 48.0);
824
825        // Clamps to MAX_REPUTATION
826        let updated = update_reputation(99.0, true, 5.0);
827        assert_eq!(updated, crate::MAX_REPUTATION);
828
829        // Clamps to MIN_REPUTATION
830        let updated = update_reputation(5.0, false, 10.0);
831        assert_eq!(updated, crate::MIN_REPUTATION);
832    }
833
834    #[test]
835    fn test_calculate_reputation_bonus() {
836        assert_eq!(calculate_reputation_bonus(0), 0.0);
837        assert_eq!(calculate_reputation_bonus(4), 1.0);
838        assert!((calculate_reputation_bonus(16) - 2.0).abs() < 0.01);
839        // Caps at 20
840        let bonus_20 = calculate_reputation_bonus(20);
841        let bonus_30 = calculate_reputation_bonus(30);
842        assert_eq!(bonus_20, bonus_30);
843    }
844
845    #[test]
846    fn test_calculate_token_bucket() {
847        let tokens = calculate_token_bucket(5.0, 10.0, 1.0, 3.0);
848        assert_eq!(tokens, 8.0);
849
850        // Caps at capacity
851        let tokens = calculate_token_bucket(8.0, 10.0, 5.0, 10.0);
852        assert_eq!(tokens, 10.0);
853    }
854
855    #[test]
856    fn test_is_rate_limit_allowed() {
857        assert!(is_rate_limit_allowed(10.0, 5.0));
858        assert!(is_rate_limit_allowed(5.0, 5.0));
859        assert!(!is_rate_limit_allowed(4.0, 5.0));
860    }
861
862    #[test]
863    fn test_calculate_sliding_window_count() {
864        let timestamps = vec![1000, 2000, 3000, 4000, 5000];
865        assert_eq!(calculate_sliding_window_count(&timestamps, 2000, 4000), 3);
866        assert_eq!(calculate_sliding_window_count(&timestamps, 1000, 5000), 5);
867        assert_eq!(calculate_sliding_window_count(&timestamps, 6000, 7000), 0);
868    }
869
870    #[test]
871    fn test_calculate_platform_fee() {
872        assert_eq!(calculate_platform_fee(1000, 0.10), 100);
873        assert_eq!(calculate_platform_fee(500, 0.05), 25);
874    }
875
876    #[test]
877    fn test_calculate_creator_share() {
878        assert_eq!(calculate_creator_share(1000, 0.20), 200);
879        assert_eq!(calculate_creator_share(500, 0.15), 75);
880    }
881
882    #[test]
883    fn test_calculate_provider_earnings() {
884        // 1000 points - 10% platform - 20% creator = 700 to provider
885        assert_eq!(calculate_provider_earnings(1000, 0.10, 0.20), 700);
886        // 500 points - 5% platform - 15% creator = 400 to provider
887        assert_eq!(calculate_provider_earnings(500, 0.05, 0.15), 400);
888    }
889
890    #[test]
891    fn test_calculate_content_price() {
892        // 1 GB at 10 points/GB with 1.0x multiplier = 10 points
893        assert_eq!(calculate_content_price(1_073_741_824, 10, 1.0), 10);
894        // 1 GB at 10 points/GB with 2.0x multiplier = 20 points
895        assert_eq!(calculate_content_price(1_073_741_824, 10, 2.0), 20);
896        // 500 MB at 10 points/GB with 1.0x multiplier = 5 points (rounded up)
897        assert_eq!(calculate_content_price(536_870_912, 10, 1.0), 5);
898    }
899
900    #[test]
901    fn test_clamp() {
902        assert_eq!(clamp(5, 0, 10), 5);
903        assert_eq!(clamp(-5, 0, 10), 0);
904        assert_eq!(clamp(15, 0, 10), 10);
905        assert_eq!(clamp(5.5, 0.0, 10.0), 5.5);
906    }
907
908    #[test]
909    fn test_lerp() {
910        assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
911        assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
912        assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
913        assert_eq!(lerp(10.0, 20.0, 0.25), 12.5);
914    }
915
916    #[test]
917    fn test_calculate_percentage_change() {
918        assert_eq!(calculate_percentage_change(100.0, 150.0), 50.0);
919        assert_eq!(calculate_percentage_change(100.0, 50.0), -50.0);
920        assert_eq!(calculate_percentage_change(100.0, 100.0), 0.0);
921        assert_eq!(calculate_percentage_change(0.0, 0.0), 0.0);
922        assert_eq!(calculate_percentage_change(0.0, 50.0), 100.0);
923    }
924
925    #[test]
926    fn test_calculate_rate() {
927        assert_eq!(calculate_rate(100, 1000), 100.0); // 100 events in 1 second = 100/sec
928        assert_eq!(calculate_rate(50, 500), 100.0); // 50 events in 0.5 seconds = 100/sec
929        assert_eq!(calculate_rate(0, 1000), 0.0);
930        assert_eq!(calculate_rate(100, 0), 0.0); // Division by zero handled
931    }
932
933    #[test]
934    fn test_normalize() {
935        assert_eq!(normalize(50.0, 0.0, 100.0), 0.5);
936        assert_eq!(normalize(0.0, 0.0, 100.0), 0.0);
937        assert_eq!(normalize(100.0, 0.0, 100.0), 1.0);
938        assert_eq!(normalize(150.0, 0.0, 100.0), 1.0); // Clamped
939        assert_eq!(normalize(-50.0, 0.0, 100.0), 0.0); // Clamped
940        assert_eq!(normalize(50.0, 50.0, 50.0), 0.5); // Same min/max
941    }
942
943    #[test]
944    fn test_average() {
945        assert_eq!(average(10.0, 20.0), 15.0);
946        assert_eq!(average(0.0, 100.0), 50.0);
947        assert_eq!(average(-10.0, 10.0), 0.0);
948    }
949
950    #[test]
951    fn test_is_within_tolerance() {
952        assert!(is_within_tolerance(10.0, 10.0, 0.0));
953        assert!(is_within_tolerance(10.5, 10.0, 1.0));
954        assert!(is_within_tolerance(9.5, 10.0, 1.0));
955        assert!(!is_within_tolerance(11.5, 10.0, 1.0));
956        assert!(!is_within_tolerance(8.5, 10.0, 1.0));
957    }
958
959    #[test]
960    fn test_bps_to_mbps() {
961        assert_eq!(bps_to_mbps(1_000_000), 1.0);
962        assert_eq!(bps_to_mbps(10_000_000), 10.0);
963        assert_eq!(bps_to_mbps(500_000), 0.5);
964    }
965
966    #[test]
967    fn test_mbps_to_bps() {
968        assert_eq!(mbps_to_bps(1.0), 1_000_000);
969        assert_eq!(mbps_to_bps(10.0), 10_000_000);
970        assert_eq!(mbps_to_bps(0.5), 500_000);
971    }
972
973    #[test]
974    fn test_calculate_uptime_percentage() {
975        assert_eq!(calculate_uptime_percentage(1000, 0), 100.0);
976        assert_eq!(calculate_uptime_percentage(1000, 100), 90.0);
977        assert_eq!(calculate_uptime_percentage(1000, 1000), 0.0);
978        assert_eq!(calculate_uptime_percentage(0, 0), 100.0);
979        assert_eq!(calculate_uptime_percentage(1000, 1500), 0.0); // Downtime > total
980    }
981
982    #[test]
983    fn test_chunk_offset() {
984        assert_eq!(chunk_offset(0, 1024), 0);
985        assert_eq!(chunk_offset(1, 1024), 1024);
986        assert_eq!(chunk_offset(10, 1024), 10240);
987        assert_eq!(chunk_offset(0, 262_144), 0);
988        assert_eq!(chunk_offset(5, 262_144), 1_310_720);
989    }
990
991    #[test]
992    fn test_byte_to_chunk_index() {
993        assert_eq!(byte_to_chunk_index(0, 1024), 0);
994        assert_eq!(byte_to_chunk_index(1023, 1024), 0);
995        assert_eq!(byte_to_chunk_index(1024, 1024), 1);
996        assert_eq!(byte_to_chunk_index(2048, 1024), 2);
997        assert_eq!(byte_to_chunk_index(1_000_000, 262_144), 3);
998        assert_eq!(byte_to_chunk_index(100, 0), 0); // Edge case: zero chunk size
999    }
1000
1001    #[test]
1002    fn test_chunk_byte_range() {
1003        assert_eq!(chunk_byte_range(0, 1024, 10240), (0, 1024));
1004        assert_eq!(chunk_byte_range(1, 1024, 10240), (1024, 2048));
1005        assert_eq!(chunk_byte_range(9, 1024, 10240), (9216, 10240)); // Last chunk
1006        assert_eq!(chunk_byte_range(10, 1024, 10240), (10240, 10240)); // Beyond end
1007    }
1008
1009    #[test]
1010    fn test_is_valid_chunk_index() {
1011        // Content size: 10240 bytes, chunk size: 1024 bytes -> 10 chunks (0-9)
1012        assert!(is_valid_chunk_index(0, 10240, 1024));
1013        assert!(is_valid_chunk_index(9, 10240, 1024));
1014        assert!(!is_valid_chunk_index(10, 10240, 1024));
1015        assert!(!is_valid_chunk_index(100, 10240, 1024));
1016
1017        // Edge case: chunk size is 0
1018        assert!(!is_valid_chunk_index(0, 10240, 0));
1019
1020        // Exact multiple
1021        assert!(is_valid_chunk_index(4, 5120, 1024)); // 5 chunks exactly
1022        assert!(!is_valid_chunk_index(5, 5120, 1024));
1023    }
1024
1025    #[test]
1026    fn test_actual_chunk_size() {
1027        // Regular chunks
1028        assert_eq!(actual_chunk_size(0, 1024, 10240), 1024);
1029        assert_eq!(actual_chunk_size(5, 1024, 10240), 1024);
1030
1031        // Last chunk (smaller) - chunk 9 starts at 9*1024=9216, remaining is 10000-9216=784
1032        assert_eq!(actual_chunk_size(9, 1024, 10000), 784);
1033
1034        // Beyond end
1035        assert_eq!(actual_chunk_size(100, 1024, 10240), 0);
1036
1037        // Exact multiple
1038        assert_eq!(actual_chunk_size(4, 1024, 5120), 1024);
1039    }
1040}