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: 2025-11-28T21:00:03+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::string_to_string)]
48#![warn(clippy::undocumented_unsafe_blocks)]
49#![warn(clippy::unicode_not_nfc)]
50#![warn(clippy::unimplemented)]
51#![warn(clippy::unseparated_literal_suffix)]
52#![warn(clippy::unused_self)]
53#![warn(clippy::unwrap_in_result)]
54#![warn(clippy::use_self)]
55#![warn(clippy::used_underscore_binding)]
56#![warn(clippy::wildcard_imports)]
57
58use core::{cmp::Ordering, ops::Neg};
59
60pub use parser::{str_to_dec, ParseDecimalError};
61pub use powers_of_ten::{checked_mul_pow_ten, mul_pow_ten, ten_pow};
62pub use rounding::{
63    i128_div_rounded, i128_mul_div_ten_pow_rounded, i128_shifted_div_rounded,
64    Round, RoundingMode,
65};
66
67mod parser;
68mod powers_of_ten;
69mod rounding;
70
71/// The maximum number of fractional decimal digits supported by `Decimal`.
72pub const MAX_N_FRAC_DIGITS: u8 = 18;
73
74#[doc(hidden)]
75#[inline]
76#[must_use]
77pub fn adjust_coeffs(x: i128, p: u8, y: i128, q: u8) -> (i128, i128) {
78    match p.cmp(&q) {
79        Ordering::Equal => (x, y),
80        Ordering::Greater => (x, mul_pow_ten(y, p - q)),
81        Ordering::Less => (mul_pow_ten(x, q - p), y),
82    }
83}
84
85#[doc(hidden)]
86#[inline]
87#[must_use]
88pub fn checked_adjust_coeffs(
89    x: i128,
90    p: u8,
91    y: i128,
92    q: u8,
93) -> (Option<i128>, Option<i128>) {
94    match p.cmp(&q) {
95        Ordering::Equal => (Some(x), Some(y)),
96        Ordering::Greater => (Some(x), checked_mul_pow_ten(y, p - q)),
97        Ordering::Less => (checked_mul_pow_ten(x, q - p), Some(y)),
98    }
99}
100
101/// Return `(q, r)` with `q = x / y` and `r = x % y`, so that `x = q * y + r`,
102/// where q is rounded against floor so that r, if non-zero, has the same sign
103/// as y and `0 <= abs(r) < abs(y)`.
104#[doc(hidden)]
105#[inline]
106#[must_use]
107#[allow(clippy::integer_division)]
108pub const fn i128_div_mod_floor(x: i128, y: i128) -> (i128, i128) {
109    let (q, r) = (x / y, x % y);
110    if (r > 0 && y < 0) || (r < 0 && y > 0) {
111        (q - 1, r + y)
112    } else {
113        (q, r)
114    }
115}
116
117// The following code is a partial copy of rust core int_log10.rs
118// TODO: remove after feature(int_log) got stable
119
120// 0 < val <= u8::MAX
121#[allow(clippy::unusual_byte_groupings)]
122#[doc(hidden)]
123#[inline]
124#[must_use]
125pub const fn u8(val: u8) -> u32 {
126    let val = val as u32;
127
128    // For better performance, avoid branches by assembling the solution
129    // in the bits above the low 8 bits.
130
131    // Adding c1 to val gives 10 in the top bits for val < 10, 11 for val >=
132    // 10
133    const C1: u32 = 0b11_00000000 - 10; // 758
134                                        // Adding c2 to val gives 01 in the top bits for val < 100, 10 for val >=
135                                        // 100
136    const C2: u32 = 0b10_00000000 - 100; // 412
137
138    // Value of top bits:
139    //            +c1  +c2  1&2
140    //     0..=9   10   01   00 = 0
141    //   10..=99   11   01   01 = 1
142    // 100..=255   11   10   10 = 2
143    ((val + C1) & (val + C2)) >> 8
144}
145
146// 0 < val < 100_000
147#[allow(clippy::unusual_byte_groupings)]
148#[doc(hidden)]
149#[inline]
150const fn less_than_5(val: u32) -> u32 {
151    // Similar to u8, when adding one of these constants to val,
152    // we get two possible bit patterns above the low 17 bits,
153    // depending on whether val is below or above the threshold.
154    const C1: u32 = 0b011_00000000000000000 - 10; // 393206
155    const C2: u32 = 0b100_00000000000000000 - 100; // 524188
156    const C3: u32 = 0b111_00000000000000000 - 1000; // 916504
157    const C4: u32 = 0b100_00000000000000000 - 10000; // 514288
158
159    // Value of top bits:
160    //                +c1  +c2  1&2  +c3  +c4  3&4   ^
161    //         0..=9  010  011  010  110  011  010  000 = 0
162    //       10..=99  011  011  011  110  011  010  001 = 1
163    //     100..=999  011  100  000  110  011  010  010 = 2
164    //   1000..=9999  011  100  000  111  011  011  011 = 3
165    // 10000..=99999  011  100  000  111  100  100  100 = 4
166    (((val + C1) & (val + C2)) ^ ((val + C3) & (val + C4))) >> 17
167}
168
169// 0 < val <= u16::MAX
170#[doc(hidden)]
171#[inline]
172#[must_use]
173pub const fn u16(val: u16) -> u32 {
174    less_than_5(val as u32)
175}
176
177// 0 < val <= u32::MAX
178#[doc(hidden)]
179#[inline]
180#[must_use]
181pub const fn u32(mut val: u32) -> u32 {
182    let mut log = 0;
183    if val >= 100_000 {
184        val /= 100_000;
185        log += 5;
186    }
187    log + less_than_5(val)
188}
189
190// 0 < val <= u64::MAX
191#[doc(hidden)]
192#[inline]
193#[must_use]
194#[allow(clippy::cast_possible_truncation)]
195pub const fn u64(mut val: u64) -> u32 {
196    let mut log = 0;
197    if val >= 10_000_000_000 {
198        val /= 10_000_000_000;
199        log += 10;
200    }
201    if val >= 100_000 {
202        val /= 100_000;
203        log += 5;
204    }
205    log + less_than_5(val as u32)
206}
207
208// 0 < val <= u128::MAX
209#[doc(hidden)]
210#[inline]
211#[must_use]
212#[allow(clippy::cast_possible_truncation)]
213pub const fn u128(mut val: u128) -> u32 {
214    let mut log = 0;
215    if val >= 100_000_000_000_000_000_000_000_000_000_000 {
216        val /= 100_000_000_000_000_000_000_000_000_000_000;
217        log += 32;
218        return log + u32(val as u32);
219    }
220    if val >= 10_000_000_000_000_000 {
221        val /= 10_000_000_000_000_000;
222        log += 16;
223    }
224    log + u64(val as u64)
225}
226
227// --- end of copied code ---
228
229#[doc(hidden)]
230#[inline]
231#[must_use]
232#[allow(clippy::cast_possible_truncation)]
233pub const fn i128_magnitude(i: i128) -> u8 {
234    // TODO: change after feature(int_log) got stable:
235    // i.log10() as u8
236    u128(i.unsigned_abs()) as u8
237}
238
239/// Return the index of the most significant bit of an u128.
240/// The given u128 must not be zero!
241fn u128_msb(mut i: u128) -> u8 {
242    debug_assert_ne!(i, 0);
243    const IDX_MAP: [u8; 16] =
244        [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4];
245    let mut n: u8 = 0;
246    if i & 0xffffffffffffffff0000000000000000_u128 != 0 {
247        n = 64;
248        i >>= 64;
249    };
250    if i & 0x0000000000000000ffffffff00000000_u128 != 0 {
251        n += 32;
252        i >>= 32;
253    };
254    if i & 0x000000000000000000000000ffff0000_u128 != 0 {
255        n += 16;
256        i >>= 16;
257    };
258    if i & 0x0000000000000000000000000000ff00_u128 != 0 {
259        n += 8;
260        i >>= 8;
261    };
262    if i & 0x000000000000000000000000000000f0_u128 != 0 {
263        n += 4;
264        i >>= 4;
265    };
266    n + IDX_MAP[i as usize] - 1
267}
268
269#[inline(always)]
270const fn u128_hi(u: u128) -> u128 {
271    u >> 64
272}
273
274#[inline(always)]
275const fn u128_lo(u: u128) -> u128 {
276    u & 0xffffffffffffffff
277}
278
279#[inline(always)]
280const fn u128_mul_u128(x: u128, y: u128) -> (u128, u128) {
281    let xh = u128_hi(x);
282    let xl = u128_lo(x);
283    let yh = u128_hi(y);
284    let yl = u128_lo(y);
285    let mut t = xl * yl;
286    let mut rl = u128_lo(t);
287    t = xl * yh + u128_hi(t);
288    let mut rh = u128_hi(t);
289    t = xh * yl + u128_lo(t);
290    rl += u128_lo(t) << 64;
291    rh += xh * yh + u128_hi(t);
292    (rh, rl)
293}
294
295// Calculate x = x / y in place, where x = xh * 2^128 + xl, and return x % y.
296// Adapted from
297// D. E. Knuth, The Art of Computer Programming, Vol. 2, Ch. 4.3.1,
298// Exercise 16
299#[inline(always)]
300#[allow(clippy::integer_division)]
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}