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 /// The Ziv certification loop exhausted its retry budget without the error interval settling
57 /// into a single rounding preimage. The returned value is **not** guaranteed correctly rounded.
58 ///
59 /// This only fires if a radius-bound estimate is wrong — a correct bound settles within a
60 /// handful of retries — and is reported instead of silently returning a possibly-1-ULP-wrong
61 /// value. At the convenience layer it panics like the other non-saturatable errors.
62 ZivRetryLimitExceeded,
63}
64
65impl Display for FpError {
66 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
67 match self {
68 FpError::InfiniteInput => {
69 f.write_str("arithmetic with an infinite input is not allowed")
70 }
71 FpError::OutOfDomain => f.write_str("the operation result is out of domain"),
72 FpError::Indeterminate => f.write_str("the operation result is an indeterminate form"),
73 FpError::Overflow(_) => f.write_str("overflow: the result is too large to represent"),
74 FpError::Underflow(_) => f.write_str("underflow: the result is too small to represent"),
75 FpError::ZivRetryLimitExceeded => f.write_str(
76 "the Ziv retry limit was exceeded; result not guaranteed correctly rounded",
77 ),
78 }
79 }
80}
81
82#[cfg(feature = "std")]
83impl std::error::Error for FpError {}
84
85/// The result of a floating point operation: a correctly-rounded value (which may be an
86/// infinity produced as a value), or an [`FpError`] when the operation cannot proceed.
87pub type FpResult<T> = Result<Rounded<T>, FpError>;
88
89#[inline]
90pub const fn assert_finite<const B: Word>(repr: &Repr<B>) {
91 if repr.is_infinite() {
92 panic_operate_with_inf()
93 }
94}
95
96#[inline]
97pub const fn assert_finite_operands<const B: Word>(lhs: &Repr<B>, rhs: &Repr<B>) {
98 if lhs.is_infinite() || rhs.is_infinite() {
99 panic_operate_with_inf()
100 }
101}
102
103/// Panics when operate with infinities
104pub const fn panic_operate_with_inf() -> ! {
105 panic!("arithmetic operations with the infinity are not allowed!")
106}
107
108/// Panics if precision is set to 0
109pub const fn assert_limited_precision(precision: usize) {
110 if precision == 0 {
111 panic_unlimited_precision()
112 }
113}
114
115/// Panics when operate on unlimited precision number
116pub const fn panic_unlimited_precision() -> ! {
117 panic!("precision cannot be 0 (unlimited) for this operation!")
118}
119
120/// Panics when taking the zeroth root of a number
121pub fn panic_root_zeroth() -> ! {
122 panic!("finding 0th root is not allowed!")
123}
124
125/// Panics when the result of an operation is NaN
126pub fn panic_nan() -> ! {
127 panic!("the result of the operation is NaN!")
128}
129
130/// Panics when an operation is out of domain (e.g. sqrt of a negative number)
131pub fn panic_out_of_domain() -> ! {
132 panic!("the operation result is out of domain!")
133}
134
135/// Panics when the Ziv retry limit is exceeded (a transcendental failed to certify its rounding;
136/// in practice this only fires if a radius-bound estimate is wrong).
137pub fn panic_ziv_retry_limit_exceeded() -> ! {
138 panic!(
139 "the Ziv retry limit was exceeded; the result is not correctly rounded! \
140 Please report this case to the maintainer."
141 )
142}
143
144impl<R: Round> Context<R> {
145 /// Unwrap an [`FpResult`], returning the value directly.
146 ///
147 /// Both [`Overflow`](FpError::Overflow) and [`Underflow`](FpError::Underflow) saturate to the
148 /// directed endpoint — outward/nearest modes reach `±∞` (overflow) or the smallest representable
149 /// (underflow); inward modes (toward-zero, opposite-infinity) reach the largest finite or signed
150 /// zero — so the saturation honors the rounding mode (e.g. `Up(pow(x, y))` agrees with
151 /// `Up(exp(y·ln x))`). Overflow panics at unlimited precision (the largest finite is undefined).
152 /// All other error variants panic (infinite input, out-of-domain, indeterminate).
153 #[inline]
154 pub fn unwrap_fp<const B: Word>(&self, result: FpResult<FBig<R, B>>) -> FBig<R, B> {
155 match result {
156 Ok(value) => value.value(),
157 Err(FpError::Overflow(sign)) => self.overflow_repr_endpoint::<B>(sign).value(),
158 Err(FpError::Underflow(sign)) => self.underflow_repr_endpoint::<B>(sign).value(),
159 Err(FpError::InfiniteInput) => panic_operate_with_inf(),
160 Err(FpError::OutOfDomain) => panic_out_of_domain(),
161 Err(FpError::Indeterminate) => panic_nan(),
162 Err(FpError::ZivRetryLimitExceeded) => panic_ziv_retry_limit_exceeded(),
163 }
164 }
165}