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}