Skip to main content

sim_lib_numbers_core/
magnitude.rs

1//! Shared magnitude limits for arbitrary-precision number libraries.
2//!
3//! These helpers keep exact-number domains finite by default while letting
4//! callers thread a different limit through their own numeric budget surfaces.
5
6use sim_kernel::{Error, Result};
7
8/// Default maximum bit length accepted for arbitrary-precision literals and
9/// results.
10pub const DEFAULT_MAX_ARBITRARY_MAGNITUDE_BITS: u64 = 262_144;
11
12/// A finite ceiling for arbitrary-precision literal and result magnitude.
13///
14/// The limit is expressed in binary bits so domains with native bit-length
15/// metadata can enforce it directly. Decimal literal parsers can use
16/// [`MagnitudeLimit::check_decimal_digits`] to conservatively estimate the same
17/// budget before allocating big integer storage.
18///
19/// # Examples
20///
21/// ```
22/// use sim_lib_numbers_core::MagnitudeLimit;
23///
24/// let limit = MagnitudeLimit::new(128);
25/// assert!(limit.check_bits("integer result", 127).is_ok());
26/// assert!(limit.check_decimal_digits("decimal literal", 1_000).is_err());
27/// ```
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub struct MagnitudeLimit {
30    max_bits: u64,
31}
32
33impl MagnitudeLimit {
34    /// Creates a finite arbitrary-precision magnitude limit.
35    pub const fn new(max_bits: u64) -> Self {
36        Self { max_bits }
37    }
38
39    /// Returns the default arbitrary-precision magnitude limit.
40    pub const fn default_arbitrary_precision() -> Self {
41        Self::new(DEFAULT_MAX_ARBITRARY_MAGNITUDE_BITS)
42    }
43
44    /// Returns the maximum allowed magnitude in binary bits.
45    pub const fn max_bits(self) -> u64 {
46        self.max_bits
47    }
48
49    /// Checks a known binary bit length against this limit.
50    pub fn check_bits(self, context: &str, estimated_bits: u64) -> Result<()> {
51        if estimated_bits > self.max_bits {
52            return Err(Error::Eval(format!(
53                "{context} magnitude too large: estimated {estimated_bits} bits exceeds limit {}",
54                self.max_bits
55            )));
56        }
57        Ok(())
58    }
59
60    /// Checks a decimal digit count against this limit, returning the estimated
61    /// binary bit length when it fits.
62    pub fn check_decimal_digits(self, context: &str, digits: usize) -> Result<u64> {
63        let estimated_bits = decimal_digits_to_bits_ceil(digits);
64        self.check_bits(context, estimated_bits)?;
65        Ok(estimated_bits)
66    }
67
68    /// Returns the largest decimal digit count conservatively accepted by this
69    /// bit budget.
70    pub fn max_decimal_digits(self) -> usize {
71        self.max_bits
72            .saturating_mul(1000)
73            .checked_div(3322)
74            .unwrap_or(0)
75            .try_into()
76            .unwrap_or(usize::MAX)
77    }
78}
79
80impl Default for MagnitudeLimit {
81    fn default() -> Self {
82        Self::default_arbitrary_precision()
83    }
84}
85
86/// Conservatively estimates the binary bit length needed for a decimal digit
87/// count.
88///
89/// The estimate rounds up `digits * log2(10)` with a fixed rational
90/// approximation. It is intentionally conservative for budget checks that must
91/// happen before parsing the decimal text into a big integer.
92pub fn decimal_digits_to_bits_ceil(digits: usize) -> u64 {
93    let digits = u64::try_from(digits).unwrap_or(u64::MAX);
94    if digits == 0 {
95        return 0;
96    }
97    digits.saturating_mul(3322).saturating_add(999) / 1000
98}