Skip to main content

fpdec_core/
lib.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2021 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source: fpdec-core/src/lib.rs $
8// $Revision: 2026-02-03T10:35:41+01:00 $
9
10#![doc = include_str ! ("../README.md")]
11#![cfg_attr(not(feature = "std"), no_std)]
12// activate some rustc lints
13#![allow(dead_code)]
14#![deny(non_ascii_idents)]
15#![deny(unsafe_code)]
16#![warn(missing_debug_implementations)]
17#![warn(missing_docs)]
18#![warn(trivial_casts, trivial_numeric_casts)]
19#![warn(unused)]
20// activate some clippy lints
21#![warn(clippy::cast_possible_truncation)]
22#![warn(clippy::cast_possible_wrap)]
23#![warn(clippy::cast_precision_loss)]
24#![warn(clippy::cast_sign_loss)]
25#![warn(clippy::cognitive_complexity)]
26#![warn(clippy::enum_glob_use)]
27#![warn(clippy::equatable_if_let)]
28#![warn(clippy::fallible_impl_from)]
29#![warn(clippy::if_not_else)]
30#![warn(clippy::if_then_some_else_none)]
31#![warn(clippy::implicit_clone)]
32#![warn(clippy::integer_division)]
33#![warn(clippy::manual_assert)]
34#![warn(clippy::match_same_arms)]
35#![warn(clippy::mismatching_type_param_order)]
36#![warn(clippy::missing_const_for_fn)]
37#![warn(clippy::missing_errors_doc)]
38#![warn(clippy::missing_panics_doc)]
39#![warn(clippy::multiple_crate_versions)]
40#![warn(clippy::multiple_inherent_impl)]
41#![warn(clippy::must_use_candidate)]
42#![warn(clippy::needless_pass_by_value)]
43#![warn(clippy::print_stderr)]
44#![warn(clippy::print_stdout)]
45#![warn(clippy::semicolon_if_nothing_returned)]
46#![warn(clippy::str_to_string)]
47#![warn(clippy::undocumented_unsafe_blocks)]
48#![warn(clippy::unicode_not_nfc)]
49#![warn(clippy::unimplemented)]
50#![warn(clippy::unseparated_literal_suffix)]
51#![warn(clippy::unused_self)]
52#![warn(clippy::unwrap_in_result)]
53#![warn(clippy::use_self)]
54#![warn(clippy::used_underscore_binding)]
55#![warn(clippy::wildcard_imports)]
56
57use core::{cmp::Ordering, ops::Neg};
58
59pub use parser::{str_to_dec, ParseDecimalError};
60pub use powers_of_ten::{checked_mul_pow_ten, mul_pow_ten, ten_pow};
61pub use rounding::{
62    i128_div_rounded, i128_mul_div_ten_pow_rounded, i128_shifted_div_rounded,
63    Round, RoundingMode,
64};
65
66mod parser;
67mod powers_of_ten;
68mod rounding;
69
70/// The maximum number of fractional decimal digits supported by `Decimal`.
71pub const MAX_N_FRAC_DIGITS: u8 = 18;
72
73#[doc(hidden)]
74#[inline]
75#[must_use]
76pub fn adjust_coeffs(x: i128, p: u8, y: i128, q: u8) -> (i128, i128) {
77    match p.cmp(&q) {
78        Ordering::Equal => (x, y),
79        Ordering::Greater => (x, mul_pow_ten(y, p - q)),
80        Ordering::Less => (mul_pow_ten(x, q - p), y),
81    }
82}
83
84#[doc(hidden)]
85#[inline]
86#[must_use]
87pub fn checked_adjust_coeffs(
88    x: i128,
89    p: u8,
90    y: i128,
91    q: u8,
92) -> (Option<i128>, Option<i128>) {
93    match p.cmp(&q) {
94        Ordering::Equal => (Some(x), Some(y)),
95        Ordering::Greater => (Some(x), checked_mul_pow_ten(y, p - q)),
96        Ordering::Less => (checked_mul_pow_ten(x, q - p), Some(y)),
97    }
98}
99
100/// Return `(q, r)` with `q = x / y` and `r = x % y`, so that `x = q * y + r`,
101/// where q is rounded against floor so that r, if non-zero, has the same sign
102/// as y and `0 <= abs(r) < abs(y)`.
103#[doc(hidden)]
104#[inline]
105#[must_use]
106#[allow(clippy::integer_division)]
107pub const fn i128_div_mod_floor(x: i128, y: i128) -> (i128, i128) {
108    let (q, r) = (x / y, x % y);
109    if (r > 0 && y < 0) || (r < 0 && y > 0) {
110        (q - 1, r + y)
111    } else {
112        (q, r)
113    }
114}
115
116// The following code is a partial copy of rust core int_log10.rs
117// TODO: remove after feature(int_log) got stable
118
119// 0 < val <= u8::MAX
120#[allow(clippy::unusual_byte_groupings)]
121#[doc(hidden)]
122#[inline]
123#[must_use]
124pub const fn u8(val: u8) -> u32 {
125    let val = val as u32;
126
127    // For better performance, avoid branches by assembling the solution
128    // in the bits above the low 8 bits.
129
130    // Adding c1 to val gives 10 in the top bits for val < 10, 11 for val >=
131    // 10
132    const C1: u32 = 0b11_00000000 - 10; // 758
133                                        // Adding c2 to val gives 01 in the top bits for val < 100, 10 for val >=
134                                        // 100
135    const C2: u32 = 0b10_00000000 - 100; // 412
136
137    // Value of top bits:
138    //            +c1  +c2  1&2
139    //     0..=9   10   01   00 = 0
140    //   10..=99   11   01   01 = 1
141    // 100..=255   11   10   10 = 2
142    ((val + C1) & (val + C2)) >> 8
143}
144
145// 0 < val < 100_000
146#[allow(clippy::unusual_byte_groupings)]
147#[doc(hidden)]
148#[inline]
149const fn less_than_5(val: u32) -> u32 {
150    // Similar to u8, when adding one of these constants to val,
151    // we get two possible bit patterns above the low 17 bits,
152    // depending on whether val is below or above the threshold.
153    const C1: u32 = 0b011_00000000000000000 - 10; // 393206
154    const C2: u32 = 0b100_00000000000000000 - 100; // 524188
155    const C3: u32 = 0b111_00000000000000000 - 1000; // 916504
156    const C4: u32 = 0b100_00000000000000000 - 10000; // 514288
157
158    // Value of top bits:
159    //                +c1  +c2  1&2  +c3  +c4  3&4   ^
160    //         0..=9  010  011  010  110  011  010  000 = 0
161    //       10..=99  011  011  011  110  011  010  001 = 1
162    //     100..=999  011  100  000  110  011  010  010 = 2
163    //   1000..=9999  011  100  000  111  011  011  011 = 3
164    // 10000..=99999  011  100  000  111  100  100  100 = 4
165    (((val + C1) & (val + C2)) ^ ((val + C3) & (val + C4))) >> 17
166}
167
168// 0 < val <= u16::MAX
169#[doc(hidden)]
170#[inline]
171#[must_use]
172pub const fn u16(val: u16) -> u32 {
173    less_than_5(val as u32)
174}
175
176// 0 < val <= u32::MAX
177#[doc(hidden)]
178#[inline]
179#[must_use]
180pub const fn u32(mut val: u32) -> u32 {
181    let mut log = 0;
182    if val >= 100_000 {
183        val /= 100_000;
184        log += 5;
185    }
186    log + less_than_5(val)
187}
188
189// 0 < val <= u64::MAX
190#[doc(hidden)]
191#[inline]
192#[must_use]
193#[allow(clippy::cast_possible_truncation)]
194pub const fn u64(mut val: u64) -> u32 {
195    let mut log = 0;
196    if val >= 10_000_000_000 {
197        val /= 10_000_000_000;
198        log += 10;
199    }
200    if val >= 100_000 {
201        val /= 100_000;
202        log += 5;
203    }
204    log + less_than_5(val as u32)
205}
206
207// 0 < val <= u128::MAX
208#[doc(hidden)]
209#[inline]
210#[must_use]
211#[allow(clippy::cast_possible_truncation)]
212pub const fn u128(mut val: u128) -> u32 {
213    let mut log = 0;
214    if val >= 100_000_000_000_000_000_000_000_000_000_000 {
215        val /= 100_000_000_000_000_000_000_000_000_000_000;
216        log += 32;
217        return log + u32(val as u32);
218    }
219    if val >= 10_000_000_000_000_000 {
220        val /= 10_000_000_000_000_000;
221        log += 16;
222    }
223    log + u64(val as u64)
224}
225
226// --- end of copied code ---
227
228#[doc(hidden)]
229#[inline]
230#[must_use]
231#[allow(clippy::cast_possible_truncation)]
232pub const fn i128_magnitude(i: i128) -> u8 {
233    // TODO: change after feature(int_log) got stable:
234    // i.log10() as u8
235    u128(i.unsigned_abs()) as u8
236}
237
238/// Return the index of the most significant bit of an u128.
239/// The given u128 must not be zero!
240fn u128_msb(mut i: u128) -> u8 {
241    debug_assert_ne!(i, 0);
242    const IDX_MAP: [u8; 16] =
243        [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
244    let mut n: u8 = 0;
245    if i & 0xffffffffffffffff0000000000000000_u128 != 0 {
246        n = 64;
247        i >>= 64;
248    };
249    if i & 0x0000000000000000ffffffff00000000_u128 != 0 {
250        n += 32;
251        i >>= 32;
252    };
253    if i & 0x000000000000000000000000ffff0000_u128 != 0 {
254        n += 16;
255        i >>= 16;
256    };
257    if i & 0x0000000000000000000000000000ff00_u128 != 0 {
258        n += 8;
259        i >>= 8;
260    };
261    if i & 0x000000000000000000000000000000f0_u128 != 0 {
262        n += 4;
263        i >>= 4;
264    };
265    n + IDX_MAP[i as usize] - 1
266}
267
268#[inline(always)]
269const fn u128_hi(u: u128) -> u128 {
270    u >> 64
271}
272
273#[inline(always)]
274const fn u128_lo(u: u128) -> u128 {
275    u & 0xffffffffffffffff
276}
277
278#[inline(always)]
279const fn u128_mul_u128(x: u128, y: u128) -> (u128, u128) {
280    let xh = u128_hi(x);
281    let xl = u128_lo(x);
282    let yh = u128_hi(y);
283    let yl = u128_lo(y);
284    let mut t = xl * yl;
285    let mut rl = u128_lo(t);
286    t = xl * yh + u128_hi(t);
287    let mut rh = u128_hi(t);
288    t = xh * yl + u128_lo(t);
289    rl += u128_lo(t) << 64;
290    rh += xh * yh + u128_hi(t);
291    (rh, rl)
292}
293
294// Calculate x = x / y in place, where x = xh * 2^128 + xl, and return x % y.
295// Adapted from
296// D. E. Knuth, The Art of Computer Programming, Vol. 2, Ch. 4.3.1,
297// Exercise 16
298#[inline(always)]
299#[allow(clippy::integer_division)]
300#[allow(clippy::missing_const_for_fn)] // false positive
301fn u256_idiv_u64(xh: &mut u128, xl: &mut u128, y: u64) -> u128 {
302    if y == 1 {
303        return 0;
304    }
305    let y = y as u128;
306    let mut th = u128_hi(*xh);
307    let mut r = th % y;
308    let mut tl = (r << 64) + u128_lo(*xh);
309    *xh = ((th / y) << 64) + tl / y;
310    r = tl % y;
311    th = (r << 64) + u128_hi(*xl);
312    r = th % y;
313    tl = (r << 64) + u128_lo(*xl);
314    *xl = ((th / y) << 64) + tl / y;
315    tl % y
316}
317
318// Calculate x = x / y in place, where x = xh * 2^128 + xl, and return x % y.
319// Specialized version adapted from
320// Henry S. Warren, Hacker’s Delight,
321// originally found at http://www.hackersdelight.org/HDcode/divlu.c.txt.
322// That code is in turn based on Algorithm D from
323// D. E. Knuth, The Art of Computer Programming, Vol. 2, Ch. 4.3.1,
324// adapted to the special case m = 4 and n = 2 and xh < y (!).
325// The link given above does not exist anymore, but the code can still be
326// found at https://github.com/hcs0/Hackers-Delight/blob/master/divlu.c.txt.
327#[inline(always)]
328#[allow(clippy::integer_division)]
329fn u256_idiv_u128_special(xh: &mut u128, xl: &mut u128, mut y: u128) -> u128 {
330    debug_assert!(*xh < y);
331    const B: u128 = 1 << 64;
332    // Normalize dividend and divisor, so that y > 2^127 (i.e. highest bit
333    // set)
334    let n_bits = 127 - u128_msb(y);
335    y <<= n_bits;
336    let yn1 = u128_hi(y);
337    let yn0 = u128_lo(y);
338    // bits to be shifted from xl to xh:
339    let sh = if n_bits == 0 {
340        0
341    } else {
342        *xl >> (128 - n_bits)
343    };
344    let xn32 = *xh << n_bits | sh;
345    let xn10 = *xl << n_bits;
346    let xn1 = u128_hi(xn10);
347    let xn0 = u128_lo(xn10);
348    let mut q1 = xn32 / yn1;
349    let mut rhat = xn32 % yn1;
350    // Now we have
351    // q1 * yn1 + rhat = xn32
352    // so that
353    // q1 * yn1 * 2^64 + rhat * 2^64 + xn1 = xn32 * 2^64 + xn1
354    while q1 >= B || q1 * yn0 > rhat * B + xn1 {
355        q1 -= 1;
356        rhat += yn1;
357        if rhat >= B {
358            break;
359        }
360    }
361    // The loop did not change the equation given above. It was terminated if
362    // either q1 < 2^64 or rhat >= 2^64 or q1 * yn0 > rhat * 2^64 + xn1.
363    // In these cases follows:
364    // q1 * yn0 <= rhat * 2^64 + xn1, therefor
365    // q1 * yn1 * 2^64 + q1 * yn0 <= xn32 * 2^64 + xn1, and
366    // q1 * y <= xn32 * 2^64 + xn1, and
367    // xn32 * 2^64 + xn1 - q1 * y >= 0.
368    // That means that the add-back step in Knuth's algorithm is not required.
369
370    // Since the final quotient is < 2^128, this must also be true for
371    // xn32 * 2^64 + xn1 - q1 * y. Thus, in the following we can safely
372    // ignore any possible overflow in xn32 * 2^64 or q1 * y.
373    let t = xn32
374        .wrapping_mul(B)
375        .wrapping_add(xn1)
376        .wrapping_sub(q1.wrapping_mul(y));
377    let mut q0 = t / yn1;
378    rhat = t % yn1;
379    while q0 >= B || q0 * yn0 > rhat * B + xn0 {
380        q0 -= 1;
381        rhat += yn1;
382        if rhat >= B {
383            break;
384        }
385    }
386    // Write back result
387    *xh = 0;
388    *xl = q1 * B + q0;
389    // Denormalize remainder
390    (t.wrapping_mul(B)
391        .wrapping_add(xn0)
392        .wrapping_sub(q0.wrapping_mul(y)))
393        >> n_bits
394}
395
396// Calculate x = x / y in place, where x = xh * 2^128 + xl, and return x % y.
397#[inline(always)]
398#[allow(clippy::cast_possible_truncation)]
399fn u256_idiv_u128(xh: &mut u128, xl: &mut u128, y: u128) -> u128 {
400    if u128_hi(y) == 0 {
401        return u256_idiv_u64(xh, xl, u128_lo(y) as u64);
402    }
403    if *xh < y {
404        return u256_idiv_u128_special(xh, xl, y);
405    }
406    let mut t = *xh % y;
407    let r = u256_idiv_u128_special(&mut t, xl, y);
408    *xh /= y;
409    r
410}
411
412/// Return `Some<(q, r)>` with `q = (x * 10^p) / y` and `r = (x * 10^p) % y`,
413/// so that `(x * 10^p) = q * y + r`, where q is rounded against floor so that
414/// r, if non-zero, has the same sign as y and `0 <= abs(r) < abs(y)`, or
415/// return `None` if |q| > i128::MAX.
416#[doc(hidden)]
417#[must_use]
418#[allow(clippy::cast_sign_loss)]
419#[allow(clippy::cast_possible_wrap)]
420pub fn i128_shifted_div_mod_floor(
421    x: i128,
422    p: u8,
423    y: i128,
424) -> Option<(i128, i128)> {
425    let (mut xh, mut xl) =
426        u128_mul_u128(x.unsigned_abs(), ten_pow(p) as u128);
427    let r = u256_idiv_u128(&mut xh, &mut xl, y.unsigned_abs());
428    if xh != 0 || xl > i128::MAX as u128 {
429        return None;
430    }
431    // xl <= i128::MAX, so xl as i128 is safe.
432    let mut q = xl as i128;
433    // r < y, so r as i128 is safe.
434    let mut r = r as i128;
435    if x.is_negative() {
436        if y.is_negative() {
437            r = r.neg();
438        } else {
439            q = q.neg() - 1;
440            r = y - r;
441        }
442    } else if y.is_negative() {
443        q = q.neg() - 1;
444        r -= y;
445    }
446    Some((q, r))
447}
448
449/// Return `Some<(q, r)>` with `q = (x1 * x2) / y` and `r = (x1 * x2) % y`,
450/// so that `(x1 * x2) = q * y + r`, where q is rounded against floor so that
451/// r, if non-zero, has the same sign as y and `0 <= abs(r) < abs(y)`, or
452/// return `None` if |q| > i128::MAX.
453#[doc(hidden)]
454#[must_use]
455#[allow(clippy::cast_possible_wrap)]
456pub fn i256_div_mod_floor(
457    x1: i128,
458    x2: i128,
459    y: i128,
460) -> Option<(i128, i128)> {
461    debug_assert!(y > 0);
462    let (mut xh, mut xl) =
463        u128_mul_u128(x1.unsigned_abs(), x2.unsigned_abs());
464    let r = u256_idiv_u128(&mut xh, &mut xl, y.unsigned_abs());
465    if xh != 0 || xl > i128::MAX as u128 {
466        return None;
467    }
468    // xl <= i128::MAX, so xl as i128 is safe.
469    let mut q = xl as i128;
470    // r < y, so r as i128 is safe.
471    let mut r = r as i128;
472    if x1.is_negative() != x2.is_negative() {
473        q = q.neg() - 1;
474        r = y - r;
475    }
476    Some((q, r))
477}