Skip to main content

puremp/
fixed_float.rs

1//! Fixed-precision floating point — a `mpfx`-style convenience wrapper over
2//! [`Float`].
3//!
4//! [`Float`] takes an explicit output precision and [`RoundingMode`] on *every*
5//! operation (the flexible MPFR-style interface). [`FixedFloat`] instead bakes a
6//! precision and rounding mode into the value, so it supports the ordinary
7//! `+ - * /` operators and method-style transcendentals without threading those
8//! parameters through each call. Binary operations use the larger of the two
9//! operands' precisions and the left operand's rounding mode.
10
11use core::cmp::Ordering;
12use core::fmt;
13
14use crate::float::{Float, RoundingMode};
15use crate::int::{Int, Sign};
16
17/// A floating-point value carrying a fixed working precision and rounding mode.
18#[derive(Clone)]
19pub struct FixedFloat {
20    value: Float,
21    mode: RoundingMode,
22}
23
24impl FixedFloat {
25    /// Wraps a [`Float`], adopting its precision and the given rounding mode.
26    #[inline]
27    pub fn from_float(value: Float, mode: RoundingMode) -> FixedFloat {
28        FixedFloat { value, mode }
29    }
30
31    /// Builds from an integer at `precision` bits.
32    pub fn from_int(n: &Int, precision: u64, mode: RoundingMode) -> FixedFloat {
33        FixedFloat {
34            value: Float::from_int(n, precision, mode),
35            mode,
36        }
37    }
38
39    /// Builds from an `f64` at `precision` bits.
40    pub fn from_f64(x: f64, precision: u64, mode: RoundingMode) -> FixedFloat {
41        FixedFloat {
42            value: Float::from_f64(x, precision, mode),
43            mode,
44        }
45    }
46
47    /// Positive zero at `precision` bits.
48    pub fn zero(precision: u64, mode: RoundingMode) -> FixedFloat {
49        FixedFloat {
50            value: Float::zero(precision),
51            mode,
52        }
53    }
54
55    /// NaN / ±∞ at `precision` bits.
56    pub fn nan(precision: u64, mode: RoundingMode) -> FixedFloat {
57        FixedFloat {
58            value: Float::nan(precision),
59            mode,
60        }
61    }
62    /// Positive infinity at `precision` bits.
63    pub fn infinity(precision: u64, mode: RoundingMode) -> FixedFloat {
64        FixedFloat {
65            value: Float::infinity(precision),
66            mode,
67        }
68    }
69    /// Negative infinity at `precision` bits.
70    pub fn neg_infinity(precision: u64, mode: RoundingMode) -> FixedFloat {
71        FixedFloat {
72            value: Float::neg_infinity(precision),
73            mode,
74        }
75    }
76
77    /// π at `precision` bits.
78    pub fn pi(precision: u64, mode: RoundingMode) -> FixedFloat {
79        FixedFloat {
80            value: Float::pi(precision, mode),
81            mode,
82        }
83    }
84    /// Euler's number e at `precision` bits.
85    pub fn e(precision: u64, mode: RoundingMode) -> FixedFloat {
86        FixedFloat {
87            value: Float::e(precision, mode),
88            mode,
89        }
90    }
91
92    // --- accessors ---
93
94    /// Returns the working precision in bits.
95    #[inline]
96    pub fn precision(&self) -> u64 {
97        self.value.precision()
98    }
99
100    /// Returns the rounding mode.
101    #[inline]
102    pub fn rounding_mode(&self) -> RoundingMode {
103        self.mode
104    }
105
106    /// Returns the underlying [`Float`].
107    #[inline]
108    pub fn as_float(&self) -> &Float {
109        &self.value
110    }
111
112    /// Consumes into the underlying [`Float`].
113    #[inline]
114    pub fn into_float(self) -> Float {
115        self.value
116    }
117
118    /// Returns the value as the nearest `f64`.
119    #[inline]
120    pub fn to_f64(&self) -> f64 {
121        self.value.to_f64()
122    }
123
124    /// Returns the shortest round-tripping decimal string.
125    #[inline]
126    pub fn to_shortest_string(&self) -> alloc::string::String {
127        self.value.to_shortest_string()
128    }
129
130    /// Returns `true` if this value is NaN.
131    #[inline]
132    pub fn is_nan(&self) -> bool {
133        self.value.is_nan()
134    }
135    /// Returns `true` if this value is `±∞`.
136    #[inline]
137    pub fn is_infinite(&self) -> bool {
138        self.value.is_infinite()
139    }
140    /// Returns `true` if this value is finite.
141    #[inline]
142    pub fn is_finite(&self) -> bool {
143        self.value.is_finite()
144    }
145    /// Returns `true` if this value is `±0`.
146    #[inline]
147    pub fn is_zero(&self) -> bool {
148        self.value.is_zero()
149    }
150    /// Returns the sign of this value.
151    #[inline]
152    pub fn sign(&self) -> Sign {
153        self.value.sign()
154    }
155
156    /// Re-rounds to a different precision (keeping the rounding mode).
157    pub fn with_precision(&self, precision: u64) -> FixedFloat {
158        FixedFloat {
159            value: self.value.round(precision, self.mode),
160            mode: self.mode,
161        }
162    }
163
164    // --- arithmetic (result precision = max of operand precisions) ---
165
166    fn combine(
167        &self,
168        rhs: &FixedFloat,
169        op: fn(&Float, &Float, u64, RoundingMode) -> Float,
170    ) -> FixedFloat {
171        let precision = self.value.precision().max(rhs.value.precision());
172        FixedFloat {
173            value: op(&self.value, &rhs.value, precision, self.mode),
174            mode: self.mode,
175        }
176    }
177
178    /// Returns `-self`.
179    pub fn neg(&self) -> FixedFloat {
180        FixedFloat {
181            value: self.value.neg(),
182            mode: self.mode,
183        }
184    }
185
186    /// Returns `|self|`.
187    pub fn abs(&self) -> FixedFloat {
188        FixedFloat {
189            value: self.value.abs(),
190            mode: self.mode,
191        }
192    }
193
194    /// Returns `√self`.
195    pub fn sqrt(&self) -> FixedFloat {
196        self.unary(Float::sqrt)
197    }
198    /// Returns `e^self`.
199    pub fn exp(&self) -> FixedFloat {
200        self.unary(Float::exp)
201    }
202    /// Returns `ln(self)`.
203    pub fn ln(&self) -> FixedFloat {
204        self.unary(Float::ln)
205    }
206    /// Returns `sin(self)`.
207    pub fn sin(&self) -> FixedFloat {
208        self.unary(Float::sin)
209    }
210    /// Returns `cos(self)`.
211    pub fn cos(&self) -> FixedFloat {
212        self.unary(Float::cos)
213    }
214    /// Returns `tan(self)`.
215    pub fn tan(&self) -> FixedFloat {
216        self.unary(Float::tan)
217    }
218    /// Returns `atan(self)`.
219    pub fn atan(&self) -> FixedFloat {
220        self.unary(Float::atan)
221    }
222
223    /// Returns `self` raised to the floating power `exp`.
224    pub fn pow(&self, exp: &FixedFloat) -> FixedFloat {
225        let precision = self.value.precision().max(exp.value.precision());
226        FixedFloat {
227            value: self.value.pow(&exp.value, precision, self.mode),
228            mode: self.mode,
229        }
230    }
231
232    fn unary(&self, op: fn(&Float, u64, RoundingMode) -> Float) -> FixedFloat {
233        FixedFloat {
234            value: op(&self.value, self.value.precision(), self.mode),
235            mode: self.mode,
236        }
237    }
238}
239
240impl PartialEq for FixedFloat {
241    #[inline]
242    fn eq(&self, other: &Self) -> bool {
243        self.value == other.value
244    }
245}
246
247impl PartialOrd for FixedFloat {
248    #[inline]
249    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
250        self.value.partial_cmp(&other.value)
251    }
252}
253
254impl fmt::Display for FixedFloat {
255    #[inline]
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        fmt::Display::fmt(&self.value, f)
258    }
259}
260
261impl fmt::Debug for FixedFloat {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        write!(f, "FixedFloat({:?}, {:?})", self.value, self.mode)
264    }
265}
266
267macro_rules! fixed_binop {
268    ($tr:ident, $m:ident, $atr:ident, $am:ident, $call:path) => {
269        impl core::ops::$tr for FixedFloat {
270            type Output = FixedFloat;
271            #[inline]
272            fn $m(self, rhs: FixedFloat) -> FixedFloat {
273                self.combine(&rhs, $call)
274            }
275        }
276        impl core::ops::$tr<&FixedFloat> for &FixedFloat {
277            type Output = FixedFloat;
278            #[inline]
279            fn $m(self, rhs: &FixedFloat) -> FixedFloat {
280                self.combine(rhs, $call)
281            }
282        }
283        impl core::ops::$atr for FixedFloat {
284            #[inline]
285            fn $am(&mut self, rhs: FixedFloat) {
286                *self = self.combine(&rhs, $call);
287            }
288        }
289    };
290}
291
292fixed_binop!(Add, add, AddAssign, add_assign, Float::add);
293fixed_binop!(Sub, sub, SubAssign, sub_assign, Float::sub);
294fixed_binop!(Mul, mul, MulAssign, mul_assign, Float::mul);
295fixed_binop!(Div, div, DivAssign, div_assign, Float::div);
296
297impl core::ops::Neg for FixedFloat {
298    type Output = FixedFloat;
299    #[inline]
300    fn neg(self) -> FixedFloat {
301        FixedFloat::neg(&self)
302    }
303}