Skip to main content

feldera_fxp/
lib.rs

1// Warn about missing docs, but not for item declared with `#[cfg(test)]`.
2#![cfg_attr(not(test), warn(missing_docs))]
3
4//! Decimal arithmetic.
5//!
6//! This crate primarily provides the [Fixed] type for decimal arithmetic with
7//! precision and scale supplied as type parameters.  It also provides
8//! [DynamicDecimal], which has 38 digits of precision and a dynamic scale.  The
9//! latter is mainly provided for serialization and does not include much in the
10//! way of arithmetic.
11//!
12//! # Features
13//!
14//! The following `cargo` features are provided:
15//!
16//! * `serde`: Implements [serde] traits for serializing and deserializing
17//!   [Fixed] and [DynamicDecimal].
18//!
19//! * `rkyv`: Implements [rkyv] traits for serializing and deserializing [Fixed]
20//!   and [DynamicDecimal].
21//!
22//! * `validation` (depends on `rkyv`): Implements [rkyv] traits for validation.
23//!
24//! * `size_of`: Implements [size_of] traits for measuring data sizes.
25//!
26//! * `dbsp` (depends on `serde`, `rkyv`, and `size_of`): Implements [DBSP]
27//!   traits for [Fixed] and [DynamicDecimal].
28//!
29//! [rkyv]: https://rkyv.org/
30//! [serde]: https://serde.rs/
31//! [size_of]: https://docs.rs/size-of/latest/size_of/
32//! [DBSP]: https://docs.rs/dbsp/latest/dbsp/
33
34use std::{cmp::Ordering, io::Write, num::IntErrorKind};
35
36use smallvec::{Array, SmallVec};
37
38#[cfg(feature = "dbsp")]
39mod dbsp_impl;
40
41#[cfg(feature = "serde")]
42mod serde_impl;
43mod u256;
44
45#[cfg(feature = "rkyv")]
46mod rkyv_impl;
47
48mod dynamic;
49pub use dynamic::DynamicDecimal;
50pub use dynamic::UniformDecimal;
51
52mod fixed;
53pub use fixed::Fixed;
54
55/// A maximum-precision `Fixed` with no decimal places.
56pub type FixedInteger = Fixed<38, 0>;
57
58fn debug_decimal(value: i128, s: usize, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59    let mut buf = SmallVec::<[u8; 64]>::new();
60    write!(&mut buf, "{:01$}", value.unsigned_abs(), s + 1).unwrap();
61    let d = buf.len() - s;
62    while buf.len() > d && buf.ends_with(b"0") {
63        buf.pop();
64    }
65    // SAFETY: `buf` contains only ASCII characters.
66    let s = unsafe { str::from_utf8_unchecked(&buf) };
67    let (integer, fraction) = s.split_at(d);
68    let sign = if value < 0 { "-" } else { "" };
69    write!(f, "{sign}{integer}")?;
70    if !fraction.is_empty() {
71        write!(f, ".{fraction}")?;
72    }
73    Ok(())
74}
75
76fn display_decimal(value: i128, s: usize, f: &mut std::fmt::Formatter) -> std::fmt::Result {
77    let mut buf = SmallVec::<[u8; 64]>::new();
78    write!(&mut buf, "{:01$}", value.abs(), s + 1).unwrap();
79    debug_assert!(buf.len() > s);
80    let decimals = if let Some(precision) = f.precision() {
81        match precision.cmp(&s) {
82            Ordering::Less => {
83                let new_len = buf.len() - (s - precision);
84                let mut discard = buf[new_len..].iter();
85                enum Rounding {
86                    Up,
87                    Down,
88                    Even,
89                }
90                impl Rounding {
91                    fn round<A>(&self, s: &mut SmallVec<A>)
92                    where
93                        A: Array<Item = u8>,
94                    {
95                        let round_up = match self {
96                            Rounding::Down => false,
97                            Rounding::Up => true,
98                            Rounding::Even => s.last().unwrap() % 2 == 1,
99                        };
100                        if round_up {
101                            let mut nines = 0;
102                            let c = loop {
103                                match s.pop() {
104                                    Some(b'9') => nines += 1,
105                                    Some(c) => break c,
106                                    None => break b'0',
107                                }
108                            };
109                            s.push(c + 1);
110                            for _ in 0..nines {
111                                s.push(b'0');
112                            }
113                        }
114                    }
115                }
116                let rounding = match discard.next().unwrap() {
117                    b'0'..=b'4' => Rounding::Down,
118                    b'5' => loop {
119                        match discard.next() {
120                            Some(b'0') => (),
121                            Some(_) => break Rounding::Up,
122                            None => break Rounding::Even,
123                        }
124                    },
125                    b'6'..=b'9' => Rounding::Up,
126                    _ => unreachable!(),
127                };
128                buf.truncate(new_len);
129                rounding.round(&mut buf);
130            }
131            Ordering::Equal => (),
132            Ordering::Greater => {
133                for _ in s..precision {
134                    buf.push(b'0');
135                }
136            }
137        }
138        precision
139    } else {
140        let mut decimals = s;
141        while decimals > 0 && buf.ends_with(b"0") {
142            buf.pop();
143            decimals -= 1;
144        }
145        decimals
146    };
147    if decimals > 0 {
148        buf.insert(buf.len() - decimals, b'.');
149    }
150
151    // SAFETY: `buf` contains only ASCII characters.
152    f.pad_integral(value >= 0, "", unsafe { str::from_utf8_unchecked(&buf) })
153}
154
155/// Parses decimal string `s` into `(sig,exp)`, representing `sig * 10**exp`.
156///
157/// Adds `scale` to the returned exponent, which is just a convenience.
158fn parse_decimal(s: &str, scale: i32) -> Result<(i128, i32), ParseDecimalError> {
159    // Accumulate digits into `value`.  Adjust `exponent` such that the
160    // parsed value is `value / 10**exponent`.
161    let mut value = 0;
162    let mut exponent = scale;
163
164    let mut saw_dot = false;
165    let mut saw_digit = false;
166
167    let mut sign = None;
168    enum Sign {
169        Positive,
170        Negative,
171    }
172
173    let mut iter = s.chars();
174    while let Some(c) = iter.next() {
175        match c {
176            '-' | '+' if sign.is_some() => return Err(ParseDecimalError::SyntaxError),
177            '-' => {
178                sign = Some(Sign::Negative);
179            }
180            '+' => {
181                sign = Some(Sign::Positive);
182            }
183            '0'..='9' => {
184                saw_digit = true;
185                if value < i128::MAX / 10 {
186                    value = value * 10 + (c as u8 - b'0') as i128;
187                    if saw_dot {
188                        exponent -= 1;
189                    }
190                } else if !saw_dot {
191                    exponent = exponent
192                        .checked_add(1)
193                        .ok_or(ParseDecimalError::OutOfRange)?;
194                }
195            }
196            '.' => {
197                if saw_dot {
198                    return Err(ParseDecimalError::SyntaxError);
199                }
200                saw_dot = true;
201            }
202            'e' | 'E' => {
203                if !saw_digit {
204                    return Err(ParseDecimalError::SyntaxError);
205                }
206                let e: i32 = match iter.as_str().parse() {
207                    Ok(e) => e,
208                    Err(error) => {
209                        return match error.kind() {
210                            IntErrorKind::Zero => unreachable!(),
211                            IntErrorKind::PosOverflow => {
212                                if value != 0 {
213                                    Err(ParseDecimalError::OutOfRange)
214                                } else {
215                                    Ok((0, 0))
216                                }
217                            }
218                            IntErrorKind::NegOverflow => Ok((0, 0)),
219                            _ => Err(ParseDecimalError::SyntaxError),
220                        };
221                    }
222                };
223                exponent = match exponent.checked_add(e) {
224                    Some(exponent) => exponent,
225                    None => {
226                        if e > 0 {
227                            // Don't see any way that `value` can be
228                            // zero, since we only have a positive
229                            // `exponent` if `value` would otherwise
230                            // overflow.
231                            debug_assert_ne!(value, 0);
232                            return Err(ParseDecimalError::OutOfRange);
233                        } else {
234                            return Ok((0, 0));
235                        }
236                    }
237                };
238                break;
239            }
240            _ => return Err(ParseDecimalError::SyntaxError),
241        }
242    }
243    if !saw_digit {
244        return Err(ParseDecimalError::SyntaxError);
245    }
246    let value = match sign {
247        Some(Sign::Negative) => -value,
248        _ => value,
249    };
250    Ok((value, exponent))
251}
252
253/// Error that can be returned when parsing [Fixed] or [DynamicDecimal].
254#[derive(Copy, Clone, Debug, PartialEq, Eq)]
255pub enum ParseDecimalError {
256    /// Invalid syntax.
257    SyntaxError,
258
259    /// Out of valid range.
260    ///
261    /// Underflow is rounded to zero, so this error is only returned when the
262    /// absolute value exceeds the type's range.
263    OutOfRange,
264}
265
266impl std::fmt::Display for ParseDecimalError {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
268        let message = match self {
269            ParseDecimalError::OutOfRange => "Value out of range",
270            ParseDecimalError::SyntaxError => "Syntax error in numeric value",
271        };
272        f.write_str(message)
273    }
274}
275
276/// Error returned for operations that would produce an out-of-range result.
277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
278pub struct OutOfRange;
279
280impl std::fmt::Display for OutOfRange {
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
282        std::fmt::Display::fmt(&"Value out of range", f)
283    }
284}
285
286/// Returns `10**exponent`, or `None` if `exponent > 38` (because the result
287/// would be greater than `i128::MAX`).
288const fn checked_pow10(exponent: u32) -> Option<i128> {
289    10i128.checked_pow(exponent)
290}
291
292/// Returns `10**exponent`.
293///
294/// # Panic
295///
296/// Panics if `exponent > 38` (because the result would be greater than
297/// `i128::MAX`).
298pub const fn pow10(exponent: usize) -> i128 {
299    10i128.checked_pow(exponent as u32).unwrap()
300}
301
302/// How to round values halfway between two integer.
303#[derive(Copy, Clone, PartialEq, Eq)]
304enum Halfway {
305    /// Round away from zero.
306    AwayFromZero,
307
308    /// Round to even.
309    Even,
310}
311
312fn round_inner(value: i128, scale: i32, n: i32, halfway: Halfway) -> Option<i128> {
313    let position = scale.saturating_sub(n);
314    if position <= 0 {
315        Some(value)
316    } else if value.abs() < 5 * pow10(position as usize - 1) {
317        Some(0)
318    } else {
319        let divisor = pow10(position as usize);
320        let quotient = value / divisor;
321        let remainder = value % divisor;
322        let round_away_from_zero = match remainder.abs().cmp(&(divisor / 2)) {
323            Ordering::Less => false,
324            Ordering::Equal => match halfway {
325                Halfway::AwayFromZero => true,
326                Halfway::Even => (quotient % 2) != 0,
327            },
328            Ordering::Greater => true,
329        };
330        let rounded_quotient = if round_away_from_zero {
331            quotient + value.signum()
332        } else {
333            quotient
334        };
335        // println!("quotient={quotient}, divisor={divisor}, remainder={remainder}, raz={round_away_from_zero}, rounded_quotient={rounded_quotient}");
336        divisor.checked_mul(rounded_quotient)
337    }
338}
339
340/// Returns `floor(x / y)`.  This is copied out of `i128::div_floor` in the
341/// standard library, which is not yet stable.
342const fn div_floor(x: i128, y: i128) -> i128 {
343    let d = x / y;
344    let r = x % y;
345
346    // If the remainder is non-zero, we need to subtract one if the
347    // signs of lhs and rhs differ, as this means we rounded upwards
348    // instead of downwards. We do this branchlessly by creating a mask
349    // which is all-ones iff the signs differ, and 0 otherwise. Then by
350    // adding this mask (which corresponds to the signed value -1), we
351    // get our correction.
352    let correction = (x ^ y) >> (i128::BITS - 1);
353    if r != 0 { d + correction } else { d }
354}
355
356/// Returns `ceil(x / y)`.  This is copied out of `i128::div_ceil` in the
357/// standard library, which is not yet stable.
358const fn div_ceil(x: i128, y: i128) -> i128 {
359    let d = x / y;
360    let r = x % y;
361
362    // When remainder is non-zero we have a.div_ceil(b) == 1 + a.div_floor(b),
363    // so we can re-use the algorithm from div_floor, just adding 1.
364    let correction = 1 + ((x ^ y) >> (i128::BITS - 1));
365    if r != 0 { d + correction } else { d }
366}
367
368/// Returns `value * 10**exponent`, rounding to even if `exponent` is negative,
369/// or `None` if the result would be out of range for `i128`.
370fn i128_mul_pow10_round_even(value: i128, exponent: i32) -> Option<i128> {
371    Some(match exponent.cmp(&0) {
372        Ordering::Less => {
373            // Divide by a negative exponent.
374            if let Some(divisor) = checked_pow10(exponent.unsigned_abs()) {
375                // Round toward even.
376                //
377                // For negative `x` and positive `y`, `x / y` rounds toward 0
378                // and `x % y` is zero or negative.
379                debug_assert!(divisor >= 2);
380                let quotient = value / divisor;
381                let remainder = value % divisor;
382                let round_away_from_zero = match remainder.abs().cmp(&(divisor / 2)) {
383                    Ordering::Less => false,
384                    Ordering::Equal => (quotient % 2) != 0,
385                    Ordering::Greater => true,
386                };
387                if round_away_from_zero {
388                    quotient + quotient.signum()
389                } else {
390                    quotient
391                }
392            } else {
393                // `10**-exponent` is greater than `i128::MAX`.  The result
394                // must be zero.
395                0
396            }
397        }
398        Ordering::Equal => value,
399        Ordering::Greater => {
400            // Multiply by a positive exponent.
401            value.checked_mul(checked_pow10(exponent.cast_unsigned())?)?
402        }
403    })
404}
405
406/// This is a doc-test to check that trying to instantiate the value 1 for a
407/// type that can't represent it properly fails, with an error like "all values
408/// of Fixed::<S,P>::one() for S >= P have magnitude less than one"
409///
410/// ```compile_fail
411/// use feldera_fxp::Fixed;
412///
413/// let _ = Fixed::<5,5>::ONE;
414/// ```
415fn _invalid_constant_test() {}
416
417#[cfg(test)]
418mod test {
419    use crate::{DynamicDecimal, Fixed, ParseDecimalError};
420    use std::fmt::Write;
421
422    #[test]
423    fn from_str() {
424        for (s, expect) in [
425            ("0", Ok(0.0)),
426            ("0.", Ok(0.0)),
427            (".0", Ok(0.0)),
428            ("-0", Ok(-0.0)),
429            ("+0", Ok(-0.0)),
430            ("--0", Err(ParseDecimalError::SyntaxError)),
431            ("-+0", Err(ParseDecimalError::SyntaxError)),
432            ("0x", Err(ParseDecimalError::SyntaxError)),
433            ("0e5x", Err(ParseDecimalError::SyntaxError)),
434            ("1.23", Ok(1.23)),
435            ("-1.23", Ok(-1.23)),
436            ("+1.23", Ok(1.23)),
437            ("99999999", Ok(9999_9999.0)),
438            ("999999999", Err(ParseDecimalError::OutOfRange)),
439            ("999999999E-1", Ok(9999_9999.9)),
440            ("9999999999e-1", Err(ParseDecimalError::OutOfRange)),
441            ("9999999999E-2", Ok(9999_9999.99)),
442            ("99999999999e-2", Err(ParseDecimalError::OutOfRange)),
443            // This fails to parse because `99999999.999` rounds up to
444            // `100000000000`, which is out of range.
445            ("99999999999e-3", Err(ParseDecimalError::OutOfRange)),
446            // But with a `1` at the end rounds down, so it stays in range.
447            ("99999999991e-3", Ok(9999_9999.99)),
448            // This value overflows the range of `i128` as an integer, so it
449            // triggers the case where we stop accepting digits and simply
450            // adjust the exponent instead.
451            (
452                "111111111111111111111111111111111111111111e-34",
453                Ok(1111_1111.11),
454            ),
455            // This value overflows the range of `i128` in the fraction, so it
456            // triggers the case where we stop accepting digits and simply
457            // adjust the exponent instead.
458            (
459                "1.23456788901234567890123456789012345678890123456",
460                Ok(1.23),
461            ),
462            // This value positively overflows the exponent.
463            ("1e999999999999999", Err(ParseDecimalError::OutOfRange)),
464            // This value positively overflows the exponent but the value is 0.
465            ("0e999999999999999", Ok(0.0)),
466            // This value negatively overflows the exponent.
467            ("1e-999999999999999", Ok(0.0)),
468            // This value overflows the range of `i128` as an integer, which
469            // starts adjusting the exponent, and then it overflows the exponent
470            // with `e`.
471            (
472                "111111111111111111111111111111111111111111e2147483644",
473                Err(ParseDecimalError::OutOfRange),
474            ),
475            // This value adjusts the exponent downward, and then it negatively
476            // overflows the exponent with `e`.
477            (
478                ".1111111111111111111111111111111111111111e-2147483648",
479                Ok(0.0),
480            ),
481            ("123e5", Ok(12_300_000.0)),
482            ("123E4", Ok(1_230_000.0)),
483            ("123e3", Ok(123_000.0)),
484            ("123e2", Ok(12_300.0)),
485            ("123e1", Ok(1_230.0)),
486            ("123e0", Ok(123.0)),
487            ("123e-1", Ok(12.3)),
488            ("123e-2", Ok(1.23)),
489            (".123", Ok(0.12)),
490            (".124", Ok(0.12)),
491            (".125", Ok(0.12)),
492            (".126", Ok(0.13)),
493            (".133", Ok(0.13)),
494            (".134", Ok(0.13)),
495            (".135", Ok(0.14)),
496            (".136", Ok(0.14)),
497        ] {
498            println!("{s}: {:?}", s.parse::<F>());
499            assert_eq!(s.parse::<F>(), expect.map(f));
500        }
501    }
502
503    #[test]
504    fn debug() {
505        fn test<const P: usize, const S: usize>(fixed: Fixed<P, S>, expect: &str) {
506            assert_eq!(format!("{fixed:?}"), expect);
507            let dynamic = DynamicDecimal::from(fixed);
508            assert_eq!(format!("{dynamic:?}"), expect);
509        }
510        test(Fixed::<20, 7>::try_from(0).unwrap(), "0");
511        test(Fixed::<20, 7>::try_from(5).unwrap(), "5");
512        test(Fixed::<20, 7>::try_from(-5).unwrap(), "-5");
513        test(Fixed::<20, 7>::try_from(10).unwrap(), "10");
514        test(Fixed::<20, 7>::try_from(0.0001).unwrap(), "0.0001");
515        test(Fixed::<20, 7>::try_from(-0.0001).unwrap(), "-0.0001");
516        test(Fixed::<20, 7>::try_from(1.0001).unwrap(), "1.0001");
517        test(Fixed::<20, 7>::try_from(-1.0001).unwrap(), "-1.0001");
518        test(Fixed::<20, 7>::try_from(1.682501).unwrap(), "1.682501");
519        test(Fixed::<20, 4>::try_from(1.6825).unwrap(), "1.6825");
520        test(Fixed::<20, 6>::try_from(1.995670).unwrap(), "1.99567");
521        test(Fixed::<20, 6>::try_from(0.995670).unwrap(), "0.99567");
522        test(Fixed::<6, 6>::try_from(0.995670).unwrap(), "0.99567");
523
524        test(Fixed::<20, 7>::try_from(-1.682501).unwrap(), "-1.682501");
525        test(Fixed::<20, 4>::try_from(-1.6825).unwrap(), "-1.6825");
526        test(Fixed::<20, 6>::try_from(-1.995670).unwrap(), "-1.99567");
527        test(Fixed::<20, 6>::try_from(-0.995670).unwrap(), "-0.99567");
528    }
529
530    #[test]
531    fn display() {
532        fn test<const P: usize, const S: usize>(fixed: Fixed<P, S>, expect: &str) {
533            let mut s = String::new();
534            write!(&mut s, "{fixed}").unwrap();
535            for precision in 0..=S + 1 {
536                write!(&mut s, " {fixed:.0$}", precision).unwrap();
537            }
538            assert_eq!(s, expect);
539
540            let dynamic = DynamicDecimal::from(fixed);
541            let mut s = String::new();
542            write!(&mut s, "{dynamic}").unwrap();
543            for precision in 0..=S + 1 {
544                write!(&mut s, " {dynamic:.0$}", precision).unwrap();
545            }
546            assert_eq!(s, expect);
547        }
548
549        test(
550            Fixed::<20, 7>::try_from(0.0001).unwrap(),
551            "0.0001 0 0.0 0.00 0.000 0.0001 0.00010 0.000100 0.0001000 0.00010000",
552        );
553        test(
554            Fixed::<20, 7>::try_from(-0.0001).unwrap(),
555            "-0.0001 -0 -0.0 -0.00 -0.000 -0.0001 -0.00010 -0.000100 -0.0001000 -0.00010000",
556        );
557        test(
558            Fixed::<20, 7>::try_from(1.0001).unwrap(),
559            "1.0001 1 1.0 1.00 1.000 1.0001 1.00010 1.000100 1.0001000 1.00010000",
560        );
561        test(
562            Fixed::<20, 7>::try_from(-1.0001).unwrap(),
563            "-1.0001 -1 -1.0 -1.00 -1.000 -1.0001 -1.00010 -1.000100 -1.0001000 -1.00010000",
564        );
565        test(
566            Fixed::<20, 7>::try_from(1.682501).unwrap(),
567            "1.682501 2 1.7 1.68 1.683 1.6825 1.68250 1.682501 1.6825010 1.68250100",
568        );
569        test(
570            Fixed::<20, 4>::try_from(1.6825).unwrap(),
571            "1.6825 2 1.7 1.68 1.682 1.6825 1.68250",
572        );
573        test(
574            Fixed::<20, 6>::try_from(1.995670).unwrap(),
575            "1.99567 2 2.0 2.00 1.996 1.9957 1.99567 1.995670 1.9956700",
576        );
577        test(
578            Fixed::<20, 6>::try_from(0.995670).unwrap(),
579            "0.99567 1 1.0 1.00 0.996 0.9957 0.99567 0.995670 0.9956700",
580        );
581        test(
582            Fixed::<6, 6>::try_from(0.995670).unwrap(),
583            "0.99567 1 1.0 1.00 0.996 0.9957 0.99567 0.995670 0.9956700",
584        );
585
586        test(
587            Fixed::<20, 7>::try_from(-1.682501).unwrap(),
588            "-1.682501 -2 -1.7 -1.68 -1.683 -1.6825 -1.68250 -1.682501 -1.6825010 -1.68250100",
589        );
590        test(
591            Fixed::<20, 4>::try_from(-1.6825).unwrap(),
592            "-1.6825 -2 -1.7 -1.68 -1.682 -1.6825 -1.68250",
593        );
594        test(
595            Fixed::<20, 6>::try_from(-1.995670).unwrap(),
596            "-1.99567 -2 -2.0 -2.00 -1.996 -1.9957 -1.99567 -1.995670 -1.9956700",
597        );
598        test(
599            Fixed::<20, 6>::try_from(-0.995670).unwrap(),
600            "-0.99567 -1 -1.0 -1.00 -0.996 -0.9957 -0.99567 -0.995670 -0.9956700",
601        );
602    }
603
604    type F = Fixed<10, 2>;
605    fn f(n: f64) -> F {
606        Fixed::try_from(n).unwrap()
607    }
608}