Skip to main content

dashu_float/
error.rs

1use dashu_base::Sign;
2use dashu_int::Word;
3
4use crate::fbig::FBig;
5use crate::repr::{Context, Repr};
6use crate::round::{Round, Rounded};
7use core::fmt::{self, Display, Formatter};
8
9/// Error returned by floating-point operations that cannot produce a usable result.
10///
11/// # Errors vs. special values
12///
13/// Infinite *outputs* (e.g. `1/0 → +inf`, `ln(0) → -inf`) are **not** errors — they are
14/// legitimate [`Exact`](dashu_base::Approximation::Exact) values produced by operations whose mathematical result is genuinely
15/// infinite. Overflow and underflow are distinct: the mathematical result is finite, but its
16/// magnitude exceeds the representable exponent range. These are reported as
17/// [`Overflow`](FpError::Overflow) / [`Underflow`](FpError::Underflow), and converted to
18/// signed infinity / signed zero at the convenience layer via `Context::unwrap_fp` (or the
19/// `Repr`-level counterpart `Context::unwrap_fp_repr`). Because the true result was finite,
20/// the converted value is always [`Inexact`](dashu_base::Approximation::Inexact) with `Rounding::NoOp`.
21///
22/// The remaining variants ([`InfiniteInput`](FpError::InfiniteInput),
23/// [`OutOfDomain`](FpError::OutOfDomain), [`Indeterminate`](FpError::Indeterminate)) signal
24/// that an operation could not proceed, and always panic at the convenience layer.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum FpError {
27    /// An operand was infinite. Infinities are terminal values: they can be produced and
28    /// compared, but not fed back into arithmetic.
29    InfiniteInput,
30
31    /// The mathematical result is not a real number (domain error), e.g. `sqrt(-x)` for `x > 0`,
32    /// `ln(-x)`, `asin(|x| > 1)`, `pow(negative, non-integer)`, an even root of a negative value.
33    OutOfDomain,
34
35    /// An indeterminate form, e.g. `0 / 0`. Only a *zero* divided by zero is
36    /// indeterminate — a non-zero value divided by zero yields ±infinity, which is a
37    /// legitimate [`Exact`](dashu_base::Approximation::Exact) value rather than an error.
38    Indeterminate,
39
40    /// The result magnitude is too large to represent as a finite number.
41    ///
42    /// At the `FBig` convenience layer this is converted to a signed infinity via
43    /// `Context::unwrap_fp` (or to a signed [`Repr`] via `Context::unwrap_fp_repr`).
44    /// The converted result is always [`Inexact`](dashu_base::Approximation::Inexact): the true result was a very large
45    /// finite number, and infinity is an approximation.
46    Overflow(Sign),
47
48    /// The result magnitude is too small to represent as a finite non-zero number.
49    ///
50    /// At the `FBig` convenience layer this is converted to a signed zero via
51    /// `Context::unwrap_fp` (or to a signed [`Repr`] via `Context::unwrap_fp_repr`).
52    /// The converted result is always [`Inexact`](dashu_base::Approximation::Inexact): the true result was a very small
53    /// non-zero number, and zero is an approximation.
54    Underflow(Sign),
55}
56
57impl Display for FpError {
58    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
59        match self {
60            FpError::InfiniteInput => {
61                f.write_str("arithmetic with an infinite input is not allowed")
62            }
63            FpError::OutOfDomain => f.write_str("the operation result is out of domain"),
64            FpError::Indeterminate => f.write_str("the operation result is an indeterminate form"),
65            FpError::Overflow(_) => f.write_str("overflow: the result is too large to represent"),
66            FpError::Underflow(_) => f.write_str("underflow: the result is too small to represent"),
67        }
68    }
69}
70
71#[cfg(feature = "std")]
72impl std::error::Error for FpError {}
73
74/// The result of a floating point operation: a correctly-rounded value (which may be an
75/// infinity produced as a value), or an [`FpError`] when the operation cannot proceed.
76pub type FpResult<T> = Result<Rounded<T>, FpError>;
77
78#[inline]
79pub const fn assert_finite<const B: Word>(repr: &Repr<B>) {
80    if repr.is_infinite() {
81        panic_operate_with_inf()
82    }
83}
84
85#[inline]
86pub const fn assert_finite_operands<const B: Word>(lhs: &Repr<B>, rhs: &Repr<B>) {
87    if lhs.is_infinite() || rhs.is_infinite() {
88        panic_operate_with_inf()
89    }
90}
91
92/// Panics when operate with infinities
93pub const fn panic_operate_with_inf() -> ! {
94    panic!("arithmetic operations with the infinity are not allowed!")
95}
96
97/// Panics if precision is set to 0
98pub const fn assert_limited_precision(precision: usize) {
99    if precision == 0 {
100        panic_unlimited_precision()
101    }
102}
103
104/// Panics when operate on unlimited precision number
105pub const fn panic_unlimited_precision() -> ! {
106    panic!("precision cannot be 0 (unlimited) for this operation!")
107}
108
109/// Panics when taking the zeroth root of a number
110pub fn panic_root_zeroth() -> ! {
111    panic!("finding 0th root is not allowed!")
112}
113
114/// Panics when the result of an operation is NaN
115pub fn panic_nan() -> ! {
116    panic!("the result of the operation is NaN!")
117}
118
119/// Panics when an operation is out of domain (e.g. sqrt of a negative number)
120pub fn panic_out_of_domain() -> ! {
121    panic!("the operation result is out of domain!")
122}
123
124impl<R: Round> Context<R> {
125    /// Unwrap an [`FpResult`], returning the value directly.
126    ///
127    /// Converts [`Overflow`](FpError::Overflow) to a signed infinity and
128    /// [`Underflow`](FpError::Underflow) to a signed zero. All other error
129    /// variants panic (infinite input, out-of-domain, indeterminate).
130    #[inline]
131    pub fn unwrap_fp<const B: Word>(&self, result: FpResult<FBig<R, B>>) -> FBig<R, B> {
132        match result {
133            Ok(value) => value.value(),
134            Err(FpError::Overflow(sign)) => FBig::new(Repr::infinity_with_sign(sign), *self),
135            Err(FpError::Underflow(sign)) => FBig::new(Repr::zero_with_sign(sign), *self),
136            Err(FpError::InfiniteInput) => panic_operate_with_inf(),
137            Err(FpError::OutOfDomain) => panic_out_of_domain(),
138            Err(FpError::Indeterminate) => panic_nan(),
139        }
140    }
141
142    /// Unwrap an [`FpResult`] at the [`Repr`] level, returning the [`Repr`] directly.
143    ///
144    /// Converts [`Overflow`](FpError::Overflow) / [`Underflow`](FpError::Underflow) to
145    /// signed infinity / signed zero; panics on all other error variants.
146    #[inline]
147    pub(crate) fn unwrap_fp_repr<const B: Word>(&self, result: FpResult<Repr<B>>) -> Repr<B> {
148        match result {
149            Ok(value) => value.value(),
150            Err(FpError::Overflow(sign)) => Repr::infinity_with_sign(sign),
151            Err(FpError::Underflow(sign)) => Repr::zero_with_sign(sign),
152            Err(FpError::InfiniteInput) => panic_operate_with_inf(),
153            Err(FpError::OutOfDomain) => panic_out_of_domain(),
154            Err(FpError::Indeterminate) => panic_nan(),
155        }
156    }
157}