scirs2_core/numeric/
precision_tracking.rs

1//! # Automated Precision Tracking for Numerical Computations
2//!
3//! This module provides comprehensive precision tracking capabilities for numerical computations,
4//! helping to monitor and control numerical accuracy throughout complex scientific calculations.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use std::collections::HashMap;
8use std::fmt;
9use std::sync::RwLock;
10use std::time::Instant;
11
12/// Precision context for tracking numerical accuracy
13#[derive(Debug, Clone)]
14pub struct PrecisionContext {
15    /// Current precision estimate (in terms of significant digits)
16    pub precision: f64,
17    /// Error bounds (relative error)
18    pub error_bounds: f64,
19    /// Number of significant digits
20    pub significant_digits: u32,
21    /// Operations that contributed to precision loss
22    pub precision_loss_sources: Vec<PrecisionLossSource>,
23    /// Condition number estimate
24    pub condition_number: Option<f64>,
25    /// Whether the computation is numerically stable
26    pub is_stable: bool,
27    /// Timestamp of last update
28    pub last_updated: Instant,
29}
30
31/// Source of precision loss in computations
32#[derive(Debug, Clone)]
33pub struct PrecisionLossSource {
34    /// Operation that caused precision loss
35    pub operation: String,
36    /// Amount of precision lost (in digits)
37    pub precision_lost: f64,
38    /// Description of the loss
39    pub description: String,
40    /// Severity of the loss
41    pub severity: PrecisionLossSeverity,
42    /// Location in the computation
43    pub location: Option<String>,
44}
45
46/// Severity of precision loss
47#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
48pub enum PrecisionLossSeverity {
49    /// Minimal precision loss (< 1 digit)
50    Minimal,
51    /// Moderate precision loss (1-3 digits)
52    Moderate,
53    /// Significant precision loss (3-6 digits)
54    Significant,
55    /// Severe precision loss (> 6 digits)
56    Severe,
57    /// Catastrophic precision loss (result unreliable)
58    Catastrophic,
59}
60
61impl fmt::Display for PrecisionLossSeverity {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::Minimal => write!(f, "Minimal"),
65            Self::Moderate => write!(f, "Moderate"),
66            Self::Significant => write!(f, "Significant"),
67            Self::Severe => write!(f, "Severe"),
68            Self::Catastrophic => write!(f, "Catastrophic"),
69        }
70    }
71}
72
73impl Default for PrecisionContext {
74    fn default() -> Self {
75        Self {
76            precision: 15.0, // Default for f64
77            error_bounds: 0.0,
78            significant_digits: 15,
79            precision_loss_sources: Vec::new(),
80            condition_number: None,
81            is_stable: true,
82            last_updated: Instant::now(),
83        }
84    }
85}
86
87impl PrecisionContext {
88    pub fn new() -> Self {
89        Self {
90            precision: 1e-15, // Default to f64 precision
91            error_bounds: 0.0,
92            significant_digits: 15,
93            precision_loss_sources: Vec::new(),
94            condition_number: None,
95            is_stable: true,
96            last_updated: Instant::now(),
97        }
98    }
99
100    /// Create a new precision context with given precision
101    pub fn with_precision(precision: f64) -> Self {
102        Self {
103            precision,
104            significant_digits: precision as u32,
105            ..Default::default()
106        }
107    }
108
109    /// Create a precision context for single precision (f32)
110    pub fn single_precision() -> Self {
111        Self::with_precision(7.0) // Typical for f32
112    }
113
114    /// Create a precision context for double precision (f64)
115    pub fn double_precision() -> Self {
116        Self::with_precision(15.0) // Typical for f64
117    }
118
119    /// Create a precision context for extended precision
120    pub fn extended_precision() -> Self {
121        Self::with_precision(18.0) // Extended precision
122    }
123
124    /// Update precision after an operation
125    pub fn update_precision(&mut self, newprecision: f64, operation: &str) {
126        if newprecision < self.precision {
127            let loss = self.precision - newprecision;
128            let severity = Self::classify_precision_loss(loss);
129
130            self.precision_loss_sources.push(PrecisionLossSource {
131                operation: operation.to_string(),
132                precision_lost: loss,
133                description: format!(
134                    "Precision reduced from {:.2} to {:.2} digits",
135                    self.precision, newprecision
136                ),
137                severity,
138                location: None,
139            });
140        }
141
142        self.precision = newprecision;
143        self.significant_digits = newprecision as u32;
144        self.is_stable = self.precision > 3.0; // Heuristic for stability
145        self.last_updated = Instant::now();
146    }
147
148    /// Record precision loss from a specific operation
149    pub fn record_precision_loss(
150        &mut self,
151        operation: &str,
152        loss: f64,
153        description: Option<String>,
154        location: Option<String>,
155    ) {
156        let severity = Self::classify_precision_loss(loss);
157
158        self.precision_loss_sources.push(PrecisionLossSource {
159            operation: operation.to_string(),
160            precision_lost: loss,
161            description: description
162                .unwrap_or_else(|| format!("Precision loss of {loss:.2} digits in {operation}")),
163            severity,
164            location,
165        });
166
167        self.precision = (self.precision - loss).max(0.0);
168        self.significant_digits = self.precision as u32;
169        self.is_stable = self.precision > 3.0;
170        self.last_updated = Instant::now();
171    }
172
173    /// Set condition number estimate
174    pub fn set_condition_number(&mut self, cond: f64) {
175        self.condition_number = Some(cond);
176
177        // Update stability based on condition number
178        if cond > 1e12 {
179            self.record_precision_loss(
180                "ill_conditioning",
181                (cond.log10() - 12.0).max(0.0),
182                Some(format!("Ill-conditioned problem (κ = {cond:.2e})")),
183                None,
184            );
185            // Force instability for very high condition numbers
186            self.is_stable = false;
187        } else if cond > 1e8 {
188            // Moderately ill-conditioned
189            self.record_precision_loss(
190                "moderate_conditioning",
191                (cond.log10() - 8.0).max(0.0),
192                Some(format!("Moderately ill-conditioned (κ = {cond:.2e})")),
193                None,
194            );
195        }
196    }
197
198    /// Get the total precision loss
199    pub fn total_precision_loss(&self) -> f64 {
200        self.precision_loss_sources
201            .iter()
202            .map(|source| source.precision_lost)
203            .sum()
204    }
205
206    /// Get the worst precision loss severity
207    pub fn worst_severity(&self) -> Option<PrecisionLossSeverity> {
208        self.precision_loss_sources
209            .iter()
210            .map(|source| &source.severity)
211            .max()
212            .cloned()
213    }
214
215    /// Check if the computation has acceptable precision
216    pub fn has_acceptable_precision(&self, minprecision: f64) -> bool {
217        self.precision >= minprecision && self.is_stable
218    }
219
220    /// Generate precision warning if necessary
221    pub fn check_acceptable(&self, threshold: f64) -> Option<PrecisionWarning> {
222        if self.precision < threshold {
223            Some(PrecisionWarning {
224                current_precision: self.precision,
225                required_precision: threshold,
226                severity: if self.precision < threshold / 2.0 {
227                    PrecisionLossSeverity::Severe
228                } else {
229                    PrecisionLossSeverity::Moderate
230                },
231                message: format!(
232                    "Precision ({:.2} digits) below acceptable threshold ({:.2} digits)",
233                    self.precision, threshold
234                ),
235                suggestions: self.generate_suggestions(),
236            })
237        } else {
238            None
239        }
240    }
241
242    /// Classify precision loss severity
243    fn classify_precision_loss(loss: f64) -> PrecisionLossSeverity {
244        if loss < 1.0 {
245            PrecisionLossSeverity::Minimal
246        } else if loss < 3.0 {
247            PrecisionLossSeverity::Moderate
248        } else if loss < 6.0 {
249            PrecisionLossSeverity::Significant
250        } else if loss < 10.0 {
251            PrecisionLossSeverity::Severe
252        } else {
253            PrecisionLossSeverity::Catastrophic
254        }
255    }
256
257    /// Generate suggestions for improving precision
258    fn generate_suggestions(&self) -> Vec<String> {
259        let mut suggestions = Vec::new();
260
261        if self.precision < 3.0 {
262            suggestions.push("Consider using higher precision arithmetic".to_string());
263            suggestions.push("Review algorithm for numerical stability".to_string());
264        }
265
266        if let Some(cond) = self.condition_number {
267            if cond > 1e10 {
268                suggestions.push("Problem is ill-conditioned; consider regularization".to_string());
269                suggestions.push("Try alternative algorithms or preconditioning".to_string());
270            }
271        }
272
273        for source in &self.precision_loss_sources {
274            match source.severity {
275                PrecisionLossSeverity::Significant
276                | PrecisionLossSeverity::Severe
277                | PrecisionLossSeverity::Catastrophic => {
278                    suggestions.push(format!(
279                        "Review {} operation for numerical stability",
280                        source.operation
281                    ));
282                }
283                _ => {}
284            }
285        }
286
287        if suggestions.is_empty() {
288            suggestions.push("Monitor precision throughout computation".to_string());
289        }
290
291        suggestions
292    }
293
294    /// Check if precision falls below a minimum threshold and return a warning
295    pub fn check_precision_warning(&self, minprecision: f64) -> Option<PrecisionWarning> {
296        if self.precision < minprecision {
297            Some(PrecisionWarning {
298                current_precision: self.precision,
299                required_precision: minprecision,
300                severity: PrecisionLossSeverity::Severe,
301                message: format!(
302                    "Precision ({:.2} digits) below required minimum ({:.2} digits)",
303                    self.precision, minprecision
304                ),
305                suggestions: vec![
306                    "Use higher precision data types".to_string(),
307                    "Consider rescaling the problem".to_string(),
308                ],
309            })
310        } else {
311            None
312        }
313    }
314}
315
316/// Warning about precision loss
317#[derive(Debug, Clone)]
318pub struct PrecisionWarning {
319    /// Current precision level
320    pub current_precision: f64,
321    /// Required precision level
322    pub required_precision: f64,
323    /// Severity of the warning
324    pub severity: PrecisionLossSeverity,
325    /// Warning message
326    pub message: String,
327    /// Suggestions for improvement
328    pub suggestions: Vec<String>,
329}
330
331impl fmt::Display for PrecisionWarning {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        writeln!(f, "Precision Warning ({}): {}", self.severity, self.message)?;
334        if !self.suggestions.is_empty() {
335            writeln!(f, "Suggestions:")?;
336            for suggestion in &self.suggestions {
337                writeln!(f, "  • {suggestion}")?;
338            }
339        }
340        Ok(())
341    }
342}
343
344/// Trait for numerical types that support precision tracking
345pub trait PrecisionTracked {
346    /// Get the current precision context
347    fn precision_context(&self) -> &PrecisionContext;
348
349    /// Get a mutable reference to the precision context
350    fn precision_context_mut(&mut self) -> &mut PrecisionContext;
351
352    /// Update precision after an operation
353    fn update_precision(&mut self, resultprecision: f64, operation: &str) {
354        self.precision_context_mut()
355            .update_precision(resultprecision, operation);
356    }
357
358    /// Record precision loss
359    fn record_loss(&mut self, operation: &str, loss: f64, description: Option<String>) {
360        self.precision_context_mut()
361            .record_precision_loss(operation, loss, description, None);
362    }
363
364    /// Check if precision is acceptable
365    fn check_precision(&self, minprecision: f64) -> CoreResult<()> {
366        if let Some(warning) = self
367            .precision_context()
368            .check_precision_warning(minprecision)
369        {
370            Err(CoreError::ValidationError(ErrorContext::new(
371                warning.message,
372            )))
373        } else {
374            Ok(())
375        }
376    }
377}
378
379/// Wrapper type for floating-point numbers with precision tracking
380#[derive(Debug, Clone)]
381pub struct TrackedFloat<T> {
382    /// The underlying value
383    pub value: T,
384    /// Precision tracking context
385    pub context: PrecisionContext,
386}
387
388impl<T> TrackedFloat<T>
389where
390    T: Copy + PartialOrd + fmt::Display,
391{
392    /// Create a new tracked float with default precision
393    pub fn new(value: T) -> Self {
394        Self {
395            value,
396            context: PrecisionContext::default(),
397        }
398    }
399
400    /// Create a new tracked float with specified precision
401    pub fn with_precision(value: T, precision: f64) -> Self {
402        Self {
403            value,
404            context: PrecisionContext::with_precision(precision),
405        }
406    }
407
408    /// Get the underlying value
409    pub fn value(&self) -> T {
410        self.value
411    }
412
413    /// Check if the value is finite (for floating-point types)
414    pub fn is_finite(&self) -> bool
415    where
416        T: num_traits::Float,
417    {
418        self.value.is_finite()
419    }
420
421    /// Check if precision is critically low
422    pub fn is_precision_critical(&self) -> bool {
423        self.context.precision < 2.0 || !self.context.is_stable
424    }
425}
426
427impl<T> PrecisionTracked for TrackedFloat<T> {
428    fn precision_context(&self) -> &PrecisionContext {
429        &self.context
430    }
431
432    fn precision_context_mut(&mut self) -> &mut PrecisionContext {
433        &mut self.context
434    }
435}
436
437/// Implement arithmetic operations with precision tracking
438impl TrackedFloat<f64> {
439    /// Add two tracked floats
440    pub fn add(&self, other: &Self) -> Self {
441        let result_value = self.value + other.value;
442        let mut result = Self::new(result_value);
443
444        // Estimate precision loss from addition
445        let min_precision = self.context.precision.min(other.context.precision);
446        let relativeerror = estimate_additionerror(self.value, other.value);
447        let precision_loss = -relativeerror.log10().max(0.0);
448
449        result.context.precision = (min_precision - precision_loss).max(0.0);
450        result.record_loss("addition", precision_loss, None);
451
452        result
453    }
454
455    /// Subtract two tracked floats
456    pub fn sub(&self, other: &Self) -> Self {
457        let result_value = self.value - other.value;
458        let mut result = Self::new(result_value);
459
460        // Check for catastrophic cancellation
461        let relative_magnitude =
462            (self.value - other.value).abs() / self.value.abs().max(other.value.abs());
463        if relative_magnitude < 1e-10 {
464            result.record_loss(
465                "subtraction",
466                10.0,
467                Some("Catastrophic cancellation detected".to_string()),
468            );
469        } else {
470            let min_precision = self.context.precision.min(other.context.precision);
471            let precision_loss = -relative_magnitude.log10().max(0.0);
472            result.context.precision = (min_precision - precision_loss).max(0.0);
473            result.record_loss("subtraction", precision_loss, None);
474        }
475
476        result
477    }
478
479    /// Multiply two tracked floats
480    pub fn mul(&self, other: &Self) -> Self {
481        let result_value = self.value * other.value;
482        let mut result = Self::new(result_value);
483
484        let min_precision = self.context.precision.min(other.context.precision);
485        let relativeerror = estimate_multiplicationerror(self.value, other.value);
486        let precision_loss = -relativeerror.log10().max(0.0);
487
488        result.context.precision = (min_precision - precision_loss).max(0.0);
489        result.record_loss("multiplication", precision_loss, None);
490
491        result
492    }
493
494    /// Divide two tracked floats
495    pub fn div(&self, other: &Self) -> CoreResult<Self> {
496        if other.value.abs() < f64::EPSILON {
497            return Err(CoreError::DomainError(ErrorContext::new(
498                "Division by zero or near-zero value",
499            )));
500        }
501
502        let result_value = self.value / other.value;
503        let mut result = Self::new(result_value);
504
505        let min_precision = self.context.precision.min(other.context.precision);
506        let relativeerror = estimate_divisionerror(self.value, other.value);
507        let precision_loss = -relativeerror.log10().max(0.0);
508
509        // Additional precision loss for small divisors
510        if other.value.abs() < 1e-10 {
511            let extra_loss = -other.value.abs().log10() - 10.0;
512            result.record_loss(
513                "division",
514                precision_loss + extra_loss,
515                Some("Division by small number".to_string()),
516            );
517        } else {
518            result.context.precision = (min_precision - precision_loss).max(0.0);
519            result.record_loss("division", precision_loss, None);
520        }
521
522        Ok(result)
523    }
524
525    /// Take square root with precision tracking
526    pub fn sqrt(&self) -> CoreResult<Self> {
527        if self.value < 0.0 {
528            return Err(CoreError::DomainError(ErrorContext::new(
529                "Square root of negative number",
530            )));
531        }
532
533        let result_value = self.value.sqrt();
534        let mut result = Self::new(result_value);
535
536        // Square root generally preserves precision well
537        let precision_loss = 0.5; // Typical loss for sqrt
538        result.context.precision = (self.context.precision - precision_loss).max(0.0);
539        result.record_loss("sqrt", precision_loss, None);
540
541        Ok(result)
542    }
543
544    /// Natural logarithm with precision tracking
545    pub fn ln(&self) -> CoreResult<Self> {
546        if self.value <= 0.0 {
547            return Err(CoreError::DomainError(ErrorContext::new(
548                "Logarithm of non-positive number",
549            )));
550        }
551
552        let result_value = self.value.ln();
553        let mut result = Self::new(result_value);
554
555        // Logarithm near 1 can cause precision loss
556        if (self.value - 1.0).abs() < 1e-10 {
557            let extra_loss = -(self.value - 1.0).abs().log10() - 10.0;
558            result.record_loss(
559                "logarithm",
560                extra_loss,
561                Some("Logarithm near 1".to_string()),
562            );
563        } else {
564            let precision_loss = 1.0; // Typical loss for log
565            result.context.precision = (self.context.precision - precision_loss).max(0.0);
566            result.record_loss("logarithm", precision_loss, None);
567        }
568
569        Ok(result)
570    }
571}
572
573/// Error estimation functions
574#[allow(dead_code)]
575fn estimate_additionerror(a: f64, b: f64) -> f64 {
576    let result = a + b;
577    if result == 0.0 {
578        f64::EPSILON
579    } else {
580        (f64::EPSILON * (a.abs() + b.abs())) / result.abs()
581    }
582}
583
584#[allow(dead_code)]
585fn estimate_multiplicationerror(_a: f64, b: f64) -> f64 {
586    2.0 * f64::EPSILON // Relative error for multiplication
587}
588
589#[allow(dead_code)]
590fn estimate_divisionerror(a: f64, b: f64) -> f64 {
591    let relerror_a = f64::EPSILON;
592    let relerror_b = f64::EPSILON;
593    (relerror_a + relerror_b) * (a / b).abs()
594}
595
596/// Global precision tracking registry
597#[derive(Debug)]
598pub struct PrecisionRegistry {
599    /// Tracked computations
600    computations: RwLock<HashMap<String, PrecisionContext>>,
601    /// Global warnings
602    warnings: RwLock<Vec<PrecisionWarning>>,
603}
604
605impl PrecisionRegistry {
606    /// Create a new precision registry
607    pub fn new() -> Self {
608        Self {
609            computations: RwLock::new(HashMap::new()),
610            warnings: RwLock::new(Vec::new()),
611        }
612    }
613
614    /// Register a computation for precision tracking
615    pub fn register_computation(&self, name: &str, context: PrecisionContext) -> CoreResult<()> {
616        let mut computations = self.computations.write().map_err(|_| {
617            CoreError::ComputationError(ErrorContext::new("Failed to acquire write lock"))
618        })?;
619        computations.insert(name.to_string(), context);
620        Ok(())
621    }
622
623    /// Update precision for a computation
624    pub fn update_computation_precision(
625        &self,
626        name: &str,
627        precision: f64,
628        operation: &str,
629    ) -> CoreResult<()> {
630        let mut computations = self.computations.write().map_err(|_| {
631            CoreError::ComputationError(ErrorContext::new("Failed to acquire write lock"))
632        })?;
633
634        if let Some(context) = computations.get_mut(name) {
635            context.update_precision(precision, operation);
636        } else {
637            return Err(CoreError::ValidationError(ErrorContext::new(format!(
638                "Computation '{name}' not found in registry"
639            ))));
640        }
641        Ok(())
642    }
643
644    /// Get precision context for a computation
645    pub fn get_computation_context(&self, name: &str) -> CoreResult<Option<PrecisionContext>> {
646        let computations = self.computations.read().map_err(|_| {
647            CoreError::ComputationError(ErrorContext::new("Failed to acquire read lock"))
648        })?;
649        Ok(computations.get(name).cloned())
650    }
651
652    /// Add a precision warning
653    pub fn add_warning(&self, warning: PrecisionWarning) -> CoreResult<()> {
654        let mut warnings = self.warnings.write().map_err(|_| {
655            CoreError::ComputationError(ErrorContext::new("Failed to acquire write lock"))
656        })?;
657        warnings.push(warning);
658        Ok(())
659    }
660
661    /// Get all warnings
662    pub fn get_warnings(&self) -> CoreResult<Vec<PrecisionWarning>> {
663        let warnings = self.warnings.read().map_err(|_| {
664            CoreError::ComputationError(ErrorContext::new("Failed to acquire read lock"))
665        })?;
666        Ok(warnings.clone())
667    }
668
669    /// Clear all warnings
670    pub fn clear_warnings(&self) -> CoreResult<()> {
671        let mut warnings = self.warnings.write().map_err(|_| {
672            CoreError::ComputationError(ErrorContext::new("Failed to acquire write lock"))
673        })?;
674        warnings.clear();
675        Ok(())
676    }
677
678    /// Generate precision report
679    pub fn generate_report(&self) -> CoreResult<String> {
680        let computations = self.computations.read().map_err(|_| {
681            CoreError::ComputationError(ErrorContext::new("Failed to acquire read lock"))
682        })?;
683        let warnings = self.warnings.read().map_err(|_| {
684            CoreError::ComputationError(ErrorContext::new("Failed to acquire read lock"))
685        })?;
686
687        let mut report = String::new();
688        report.push_str("=== Precision Tracking Report ===\n\n");
689
690        if computations.is_empty() {
691            report.push_str("No computations tracked.\n");
692        } else {
693            report.push_str("Tracked Computations:\n");
694            for (name, context) in computations.iter() {
695                report.push_str(&format!(
696                    "  {}: {:.2} digits, {} sources of loss, stable: {}\n",
697                    name,
698                    context.precision,
699                    context.precision_loss_sources.len(),
700                    context.is_stable
701                ));
702            }
703        }
704
705        if !warnings.is_empty() {
706            report.push_str(&format!("\nWarnings ({}):\n", warnings.len()));
707            for warning in warnings.iter() {
708                report.push_str(&format!("  {}\n", warning.message));
709            }
710        }
711
712        Ok(report)
713    }
714}
715
716impl Default for PrecisionRegistry {
717    fn default() -> Self {
718        Self::new()
719    }
720}
721
722/// Global precision registry instance
723static GLOBAL_PRECISION_REGISTRY: std::sync::LazyLock<PrecisionRegistry> =
724    std::sync::LazyLock::new(PrecisionRegistry::new);
725
726/// Get the global precision registry
727#[allow(dead_code)]
728pub fn global_precision_registry() -> &'static PrecisionRegistry {
729    &GLOBAL_PRECISION_REGISTRY
730}
731
732/// Convenience macros for precision tracking
733#[macro_export]
734macro_rules! track_precision {
735    ($name:expr, $precision:expr) => {
736        $crate::numeric::precision_tracking::global_precision_registry()
737            .register_computation(
738                $name,
739                $crate::numeric::precision_tracking::PrecisionContext::new($precision),
740            )
741            .unwrap_or_else(|e| eprintln!("Failed to track precision: {:?}", e));
742    };
743}
744
745#[macro_export]
746macro_rules! update_precision {
747    ($name:expr, $precision:expr, $operation:expr) => {
748        $crate::numeric::precision_tracking::global_precision_registry()
749            .update_computation_precision($name, $precision, $operation)
750            .unwrap_or_else(|e| eprintln!("Failed to update precision: {:?}", e));
751    };
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757
758    #[test]
759    fn test_precision_context() {
760        let mut context = PrecisionContext::double_precision();
761        assert_eq!(context.precision, 15.0);
762        assert!(context.is_stable);
763
764        context.update_precision(10.0, "test_operation");
765        assert_eq!(context.precision, 10.0);
766        assert_eq!(context.precision_loss_sources.len(), 1);
767    }
768
769    #[test]
770    fn test_tracked_float_arithmetic() {
771        let a = TrackedFloat::with_precision(1.0, 15.0);
772        let b = TrackedFloat::with_precision(1e-15, 15.0);
773
774        let result = a.add(&b);
775        // Addition of very different magnitudes should show some precision loss or be tracked
776        assert!(
777            result.context.precision <= 15.0,
778            "Precision should not increase, got: {}",
779            result.context.precision
780        );
781        // Result should be close to 1.0 + 1e-15, which should be handled correctly
782        let expected = 1.0 + 1e-15;
783        assert!(
784            (result.value - expected).abs() < 1e-14,
785            "Expected {}, got {}",
786            expected,
787            result.value
788        );
789    }
790
791    #[test]
792    fn test_catastrophic_cancellation() {
793        let a = TrackedFloat::with_precision(1.000_000_000_000_1, 15.0);
794        let b = TrackedFloat::with_precision(1.0, 15.0);
795
796        let result = a.sub(&b);
797        // Subtraction of nearly equal numbers should show significant precision loss
798        assert!(result.context.precision < 10.0);
799        assert!(!result.context.precision_loss_sources.is_empty());
800    }
801
802    #[test]
803    fn test_division_by_small_number() {
804        let a = TrackedFloat::with_precision(1.0, 15.0);
805        let b = TrackedFloat::with_precision(1e-12, 15.0);
806
807        let result = a.div(&b).expect("Division should succeed for test values");
808        // Division by small number should show precision loss
809        assert!(result.context.precision < 15.0);
810    }
811
812    #[test]
813    fn test_precision_warning() {
814        let context = PrecisionContext::with_precision(2.0);
815        let warning = context.check_precision_warning(5.0);
816        assert!(warning.is_some());
817
818        let warning = warning.expect("Warning should be present when precision is lost");
819        assert_eq!(warning.current_precision, 2.0);
820        assert_eq!(warning.required_precision, 5.0);
821    }
822
823    #[test]
824    fn test_precision_registry() {
825        let registry = PrecisionRegistry::new();
826        let context = PrecisionContext::with_precision(10.0);
827
828        registry
829            .register_computation("test", context)
830            .expect("Registering computation should succeed");
831
832        let retrieved = registry
833            .get_computation_context("test")
834            .expect("Retrieving registered computation should succeed");
835        assert!(retrieved.is_some());
836        assert_eq!(
837            retrieved.expect("Retrieved context should exist").precision,
838            10.0
839        );
840    }
841
842    #[test]
843    fn test_sqrt_precision() {
844        let a = TrackedFloat::with_precision(4.0, 15.0);
845        let result = a
846            .sqrt()
847            .expect("Square root of positive number should succeed");
848        assert_eq!(result.value, 2.0);
849        assert!(result.context.precision < 15.0); // Some precision loss expected
850    }
851
852    #[test]
853    fn test_ln_near_one() {
854        let a = TrackedFloat::with_precision(1.0 + 1e-12, 15.0);
855        let result = a
856            .ln()
857            .expect("Natural log of positive number should succeed");
858        // Logarithm near 1 should show some precision loss
859        assert!(
860            result.context.precision < 15.0,
861            "Expected precision loss for ln near 1, got precision: {}",
862            result.context.precision
863        );
864        // Should have precision loss sources recorded
865        assert!(
866            !result.context.precision_loss_sources.is_empty(),
867            "Should have recorded precision loss sources"
868        );
869    }
870
871    #[test]
872    fn test_condition_number() {
873        let mut context = PrecisionContext::double_precision();
874        // Verify initial state
875        assert!(context.is_stable);
876        assert!(context.precision_loss_sources.is_empty());
877
878        context.set_condition_number(1e15);
879
880        // After setting high condition number, context should be unstable
881        // and should have precision loss sources
882        assert!(
883            !context.is_stable,
884            "Context should be unstable with condition number 1e15"
885        );
886        assert!(
887            !context.precision_loss_sources.is_empty(),
888            "Should have precision loss sources after setting high condition number"
889        );
890        assert!(context.condition_number.is_some());
891        assert_eq!(
892            context
893                .condition_number
894                .expect("Condition number should be set"),
895            1e15
896        );
897    }
898
899    #[test]
900    fn test_precision_loss_severity() {
901        assert_eq!(
902            PrecisionContext::classify_precision_loss(0.5),
903            PrecisionLossSeverity::Minimal
904        );
905        assert_eq!(
906            PrecisionContext::classify_precision_loss(2.0),
907            PrecisionLossSeverity::Moderate
908        );
909        assert_eq!(
910            PrecisionContext::classify_precision_loss(5.0),
911            PrecisionLossSeverity::Significant
912        );
913        assert_eq!(
914            PrecisionContext::classify_precision_loss(8.0),
915            PrecisionLossSeverity::Severe
916        );
917        assert_eq!(
918            PrecisionContext::classify_precision_loss(12.0),
919            PrecisionLossSeverity::Catastrophic
920        );
921    }
922}