Skip to main content

decimal_scaled/
consts.rs

1//! Mathematical constants and float-compatibility constants for every
2//! decimal width.
3//!
4//! # Constants provided
5//!
6//! The [`DecimalConsts`] trait exposes `pi`, `tau`, `half_pi`,
7//! `quarter_pi`, `golden`, and `e` as methods on every width. The
8//! native-tier (`D38` and narrower) impls live here against a 37-digit
9//! `i128` reference; the wide tier (`D76` / `D153` / `D307`) impls
10//! live in `consts_wide.rs` against per-tier raw constants stored at
11//! each storage type's maximum precision.
12//!
13//! Two inherent associated constants, `EPSILON` and `MIN_POSITIVE`, are
14//! provided as analogues to `f64::EPSILON` and `f64::MIN_POSITIVE` so
15//! that generic code parameterised over numeric types continues to
16//! compile when `T` is any of the decimal widths.
17//!
18//! # Precision strategy
19//!
20//! All constant values are derived from a 37-digit reference stored as a
21//! raw `i128` at `SCALE_REF = 37`. They do not pass through `f64` at any
22//! point. The rescale from `SCALE_REF` to the caller's `SCALE` uses
23//! integer division with the crate-default [`RoundingMode`] (half-to-even
24//! by default; overridable via the `rounding-*` Cargo features).
25//!
26//! Going through `f64` would cap precision at roughly 15-17 decimal digits
27//! (f64 mantissa width). The raw-i128 path preserves up to 37 digits, which
28//! exceeds every practical scale value.
29//!
30//! At `SCALE > SCALE_REF` (i.e. `SCALE > 37`) the constant is multiplied
31//! up from the reference, so trailing digits are zero-extended and carry no
32//! additional precision. At `SCALE = 38` the multiplication may overflow
33//! `i128` for some constants; callers that need `SCALE > 37` should verify
34//! that the result is in range.
35//!
36//! # Why `SCALE_REF = 37`?
37//!
38//! The maximum `SCALE_REF` that keeps every constant in `i128`. The
39//! largest constant, `tau ≈ 6.28...`, lands at ~6.28×10³⁷ — still
40//! below `i128::MAX ≈ 1.7×10³⁸`. `SCALE_REF = 38` would overflow
41//! `tau`/`pi`/`e`/`golden`. The smaller constants `half_pi` and
42//! `quarter_pi` could individually hold 38 frac digits, but a single
43//! shared `SCALE_REF` keeps the rescale helpers uniform; the
44//! per-constant precision gain (1 digit, on two constants) is not
45//! worth six independent rescale paths.
46//!
47//! At `SCALE_REF = 37` the constants are accurate to within 0.5 ULP
48//! for every `SCALE ≤ 37`. At `SCALE = 38` (the D38 maximum) the
49//! result is off by up to ≈ 5 ULP — three of the supported scales
50//! would need a raw constant wider than `i128` can hold. Tightening
51//! to 0.5 ULP at `SCALE = 38` (and for the wide tiers' deeper
52//! scales) requires per-width raw constants stored at the storage
53//! type's maximum precision; recorded as a follow-up.
54//!
55//! [`RoundingMode`]: crate::rounding::RoundingMode
56//!
57//! # Sources
58//!
59//! Each raw constant is the half-to-even rounding of the canonical
60//! decimal expansion to 37 fractional digits. ISO 80000-2 (pi, tau,
61//! pi/2, pi/4), OEIS A001113 (e), OEIS A001622 (golden ratio).
62
63use crate::core_type::D38;
64
65/// Reference scale for the high-precision raw constants below.
66///
67/// Every constant fits in `i128` at this scale; the largest
68/// (tau ≈ 6.28×10³⁷) is below `i128::MAX ≈ 1.7×10³⁸`. Caller scales
69/// above this value rescale up by `10^(SCALE - SCALE_REF)`, which
70/// appends placeholder zeros without adding precision — `SCALE = 38`
71/// loses up to ≈ 5 ULP this way. Caller scales at or below
72/// `SCALE_REF` rescale down via the crate-default [`RoundingMode`],
73/// preserving the 0.5 ULP contract.
74///
75/// # Precision
76///
77/// N/A: constant value, no arithmetic performed.
78///
79/// [`RoundingMode`]: crate::rounding::RoundingMode
80const SCALE_REF: u32 = 37;
81
82// Raw i128 constants at SCALE_REF = 37, materialised at build time
83// by `build.rs` (the same hand-rolled multi-precision generator that
84// emits the wide-tier constants). Sources: ISO 80000-2 (pi, tau,
85// pi/2, pi/4), OEIS A001113 (e), OEIS A001622 (golden ratio).
86//
87// The build-time string -> i128 parse is `const fn` (Rust 1.83+).
88
89include!(concat!(env!("OUT_DIR"), "/wide_consts.rs"));
90
91const PI_RAW_S37: i128 = match i128::from_str_radix(PI_D38_S37, 10) {
92    Ok(v) => v,
93    Err(_) => panic!("consts: PI_D38_S37 not parseable"),
94};
95const TAU_RAW_S37: i128 = match i128::from_str_radix(TAU_D38_S37, 10) {
96    Ok(v) => v,
97    Err(_) => panic!("consts: TAU_D38_S37 not parseable"),
98};
99const HALF_PI_RAW_S37: i128 = match i128::from_str_radix(HALF_PI_D38_S37, 10) {
100    Ok(v) => v,
101    Err(_) => panic!("consts: HALF_PI_D38_S37 not parseable"),
102};
103const QUARTER_PI_RAW_S37: i128 = match i128::from_str_radix(QUARTER_PI_D38_S37, 10) {
104    Ok(v) => v,
105    Err(_) => panic!("consts: QUARTER_PI_D38_S37 not parseable"),
106};
107const E_RAW_S37: i128 = match i128::from_str_radix(E_D38_S37, 10) {
108    Ok(v) => v,
109    Err(_) => panic!("consts: E_D38_S37 not parseable"),
110};
111const GOLDEN_RAW_S37: i128 = match i128::from_str_radix(GOLDEN_D38_S37, 10) {
112    Ok(v) => v,
113    Err(_) => panic!("consts: GOLDEN_D38_S37 not parseable"),
114};
115
116// Rescaling from SCALE_REF to the caller's SCALE is delegated to
117// `D38::rescale` (which uses round-half-to-even by default; see
118// `src/rescale.rs`). The constants below construct a `D38<SCALE_REF>`
119// from the raw integer literal and then rescale to the caller's
120// `D38<SCALE>`.
121
122/// Well-known mathematical constants available on any [`D38<SCALE>`].
123///
124/// Import this trait to call `D38s12::pi()`, `D38s12::e()`, etc.
125///
126/// All returned values are computed from a 37-digit raw-`i128` reference
127/// without passing through `f64`. The result is bit-exact at the target
128/// `SCALE` for every supported scale up to `SCALE = 37`.
129pub trait DecimalConsts: Sized {
130    /// Pi (~3.14159265...). One half-turn in radians.
131    ///
132    /// Source: ISO 80000-2 / OEIS A000796. 37-digit reference rescaled to
133    /// `SCALE` via the crate-default rounding mode.
134    ///
135    /// # Precision
136    ///
137    /// N/A: constant value, no arithmetic performed.
138    fn pi() -> Self;
139
140    /// Tau (~6.28318530...). One full turn in radians.
141    ///
142    /// Defined as `2 * pi`. 37-digit reference rescaled to `SCALE` via the crate-default rounding mode.
143    ///
144    /// # Precision
145    ///
146    /// N/A: constant value, no arithmetic performed.
147    fn tau() -> Self;
148
149    /// Half-pi (~1.57079632...). One quarter-turn in radians.
150    ///
151    /// Defined as `pi / 2`. 37-digit reference rescaled to `SCALE` via the crate-default rounding mode.
152    ///
153    /// # Precision
154    ///
155    /// N/A: constant value, no arithmetic performed.
156    fn half_pi() -> Self;
157
158    /// Quarter-pi (~0.78539816...). One eighth-turn in radians.
159    ///
160    /// Defined as `pi / 4`. 37-digit reference rescaled to `SCALE` via the crate-default rounding mode.
161    ///
162    /// # Precision
163    ///
164    /// N/A: constant value, no arithmetic performed.
165    fn quarter_pi() -> Self;
166
167    /// The golden ratio (~1.61803398...). Dimensionless.
168    ///
169    /// Defined as `(1 + sqrt(5)) / 2`. Source: OEIS A001622. 35-digit
170    /// reference rescaled to `SCALE` via the crate-default rounding mode.
171    ///
172    /// # Precision
173    ///
174    /// N/A: constant value, no arithmetic performed.
175    fn golden() -> Self;
176
177    /// Euler's number (~2.71828182...). Dimensionless.
178    ///
179    /// Source: OEIS A001113. 37-digit reference rescaled to `SCALE` via the crate-default rounding mode.
180    ///
181    /// # Precision
182    ///
183    /// N/A: constant value, no arithmetic performed.
184    fn e() -> Self;
185}
186
187// Public-to-crate helpers that return each constant's rescaled bits at
188// the caller's target SCALE. Used by the `decl_decimal_consts!` macro
189// to provide DecimalConsts for narrower widths (D9, D18) without
190// duplicating the rescale logic.
191
192pub(crate) fn pi_at_target<const TARGET: u32>() -> i128 {
193    D38::<SCALE_REF>::from_bits(PI_RAW_S37).rescale::<TARGET>().to_bits()
194}
195pub(crate) fn tau_at_target<const TARGET: u32>() -> i128 {
196    D38::<SCALE_REF>::from_bits(TAU_RAW_S37).rescale::<TARGET>().to_bits()
197}
198pub(crate) fn half_pi_at_target<const TARGET: u32>() -> i128 {
199    D38::<SCALE_REF>::from_bits(HALF_PI_RAW_S37).rescale::<TARGET>().to_bits()
200}
201pub(crate) fn quarter_pi_at_target<const TARGET: u32>() -> i128 {
202    D38::<SCALE_REF>::from_bits(QUARTER_PI_RAW_S37).rescale::<TARGET>().to_bits()
203}
204pub(crate) fn golden_at_target<const TARGET: u32>() -> i128 {
205    D38::<SCALE_REF>::from_bits(GOLDEN_RAW_S37).rescale::<TARGET>().to_bits()
206}
207pub(crate) fn e_at_target<const TARGET: u32>() -> i128 {
208    D38::<SCALE_REF>::from_bits(E_RAW_S37).rescale::<TARGET>().to_bits()
209}
210
211// The `DecimalConsts` impl for `D38<SCALE>` is emitted by the
212// `decl_decimal_consts!` macro — the same macro D9 / D18 / D76+ use.
213// It expands to `Self(pi_at_target::<SCALE>())` etc., which is
214// identical to the previous hand-coded
215// `D38::<SCALE_REF>::from_bits(PI_RAW_S37).rescale::<SCALE>()` because
216// `pi_at_target` is defined as exactly that, then `.to_bits()`.
217crate::macros::consts::decl_decimal_consts!(D38, i128);
218
219// Inherent associated constants: EPSILON / MIN_POSITIVE.
220//
221// These mirror `f64::EPSILON` and `f64::MIN_POSITIVE` so that generic
222// numeric code that calls `T::EPSILON` or `T::MIN_POSITIVE` compiles
223// when `T = D38<SCALE>`. For D38 both equal `D38(1)` -- the smallest
224// representable positive value (1 LSB = 10^-SCALE). There are no subnormals.
225
226impl<const SCALE: u32> D38<SCALE> {
227    /// Smallest representable positive value: 1 LSB = `10^-SCALE`.
228    ///
229    /// Provided as an analogue to `f64::EPSILON` for generic numeric code.
230    /// Note that this differs from the f64 definition ("difference between
231    /// 1.0 and the next-larger f64"): for `D38` the LSB is uniform across
232    /// the entire representable range.
233    ///
234    /// # Precision
235    ///
236    /// N/A: constant value, no arithmetic performed.
237    pub const EPSILON: Self = Self(1);
238
239    /// Smallest positive value (equal to [`Self::EPSILON`]).
240    ///
241    /// Provided as an analogue to `f64::MIN_POSITIVE` for generic numeric
242    /// code. Unlike `f64`, `D38` has no subnormals, so `MIN_POSITIVE`
243    /// and `EPSILON` are the same value.
244    ///
245    /// # Precision
246    ///
247    /// N/A: constant value, no arithmetic performed.
248    pub const MIN_POSITIVE: Self = Self(1);
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254    use crate::core_type::D38s12;
255
256    // Bit-exact assertions at SCALE = 12.
257    //
258    // At SCALE = 12 each constant is the 37-digit raw integer divided by
259    // 10^23, rounded half-to-even.
260
261    /// pi at SCALE=12: raw / 10^23.
262    /// Truncated 13 digits: 3_141_592_653_589.
263    /// 14th digit is 7 (from position 14 of the raw) -> round up.
264    /// Expected: 3_141_592_653_590.
265    #[test]
266    fn pi_is_bit_exact_at_scale_12() {
267        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
268        assert_eq!(D38s12::pi().to_bits(), 3_141_592_653_590_i128);
269    }
270
271    /// tau at SCALE=12: raw / 10^23.
272    /// Truncated 13 digits: 6_283_185_307_179.
273    /// 14th digit is 5 -> round up. Expected: 6_283_185_307_180.
274    #[test]
275    fn tau_is_bit_exact_at_scale_12() {
276        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
277        assert_eq!(D38s12::tau().to_bits(), 6_283_185_307_180_i128);
278    }
279
280    /// half_pi at SCALE=12: raw / 10^23.
281    /// Truncated 13 digits: 1_570_796_326_794.
282    /// 14th digit is 8 -> round up. Expected: 1_570_796_326_795.
283    #[test]
284    fn half_pi_is_bit_exact_at_scale_12() {
285        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
286        assert_eq!(D38s12::half_pi().to_bits(), 1_570_796_326_795_i128);
287    }
288
289    /// quarter_pi at SCALE=12: raw / 10^23.
290    /// Truncated 12 digits: 785_398_163_397.
291    /// 13th digit is 4 -> no round-up. Expected: 785_398_163_397.
292    #[test]
293    fn quarter_pi_is_bit_exact_at_scale_12() {
294        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
295        assert_eq!(D38s12::quarter_pi().to_bits(), 785_398_163_397_i128);
296    }
297
298    /// e at SCALE=12: raw / 10^23.
299    /// Truncated 13 digits: 2_718_281_828_459.
300    /// 14th digit is 0 -> no round-up. Expected: 2_718_281_828_459.
301    #[test]
302    fn e_is_bit_exact_at_scale_12() {
303        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
304        assert_eq!(D38s12::e().to_bits(), 2_718_281_828_459_i128);
305    }
306
307    /// golden at SCALE=12: raw / 10^23.
308    /// Truncated 13 digits: 1_618_033_988_749.
309    /// 14th digit is 8 -> round up. Expected: 1_618_033_988_750.
310    #[test]
311    fn golden_is_bit_exact_at_scale_12() {
312        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
313        assert_eq!(D38s12::golden().to_bits(), 1_618_033_988_750_i128);
314    }
315
316    // Closeness checks against core::f64::consts.
317    // These verify that the correct reference digits were selected; the
318    // bit-exact tests above are the primary acceptance criteria.
319
320    /// pi() converted to f64 is within 1e-11 of `core::f64::consts::PI`.
321    /// At SCALE=12, 1 LSB = 1e-12, so 1e-11 covers rescale rounding plus
322    /// the f64 conversion step.
323    #[test]
324    fn pi_close_to_f64_pi() {
325        let diff = (D38s12::pi().to_f64() - core::f64::consts::PI).abs();
326        assert!(diff < 1e-11, "pi diverges from f64 PI by {diff}");
327    }
328
329    #[test]
330    fn tau_close_to_f64_tau() {
331        let diff = (D38s12::tau().to_f64() - core::f64::consts::TAU).abs();
332        assert!(diff < 1e-11, "tau diverges from f64 TAU by {diff}");
333    }
334
335    #[test]
336    fn half_pi_close_to_f64_frac_pi_2() {
337        let diff =
338            (D38s12::half_pi().to_f64() - core::f64::consts::FRAC_PI_2).abs();
339        assert!(diff < 1e-11, "half_pi diverges from f64 FRAC_PI_2 by {diff}");
340    }
341
342    #[test]
343    fn quarter_pi_close_to_f64_frac_pi_4() {
344        let diff =
345            (D38s12::quarter_pi().to_f64() - core::f64::consts::FRAC_PI_4).abs();
346        assert!(
347            diff < 1e-11,
348            "quarter_pi diverges from f64 FRAC_PI_4 by {diff}"
349        );
350    }
351
352    #[test]
353    fn e_close_to_f64_e() {
354        let diff = (D38s12::e().to_f64() - core::f64::consts::E).abs();
355        assert!(diff < 1e-11, "e diverges from f64 E by {diff}");
356    }
357
358    /// golden() converted to f64 is within 1e-11 of the closed form
359    /// `(1 + sqrt(5)) / 2`. Requires std for `f64::sqrt`.
360    #[cfg(feature = "std")]
361    #[test]
362    fn golden_close_to_closed_form() {
363        let expected = (1.0_f64 + 5.0_f64.sqrt()) / 2.0;
364        let diff = (D38s12::golden().to_f64() - expected).abs();
365        assert!(diff < 1e-11, "golden diverges from closed-form by {diff}");
366    }
367
368    // EPSILON / MIN_POSITIVE
369
370    #[test]
371    fn epsilon_is_one_ulp() {
372        assert_eq!(D38s12::EPSILON.to_bits(), 1_i128);
373        assert!(D38s12::EPSILON > D38s12::ZERO);
374    }
375
376    #[test]
377    fn min_positive_is_one_ulp() {
378        assert_eq!(D38s12::MIN_POSITIVE.to_bits(), 1_i128);
379        assert_eq!(D38s12::MIN_POSITIVE, D38s12::EPSILON);
380    }
381
382    /// At SCALE = 6 the LSB is 10^-6; EPSILON is still raw 1.
383    #[test]
384    fn epsilon_at_scale_6_is_one_ulp() {
385        type D6 = D38<6>;
386        assert_eq!(D6::EPSILON.to_bits(), 1_i128);
387        assert_eq!(D6::MIN_POSITIVE.to_bits(), 1_i128);
388    }
389
390    // Cross-scale exercises
391
392    /// At SCALE = 6, pi() should equal 3.141593 (rounded half-to-even from
393    /// 3.1415926535...). Expected raw bits: 3_141_593.
394    #[test]
395    fn pi_at_scale_6_is_bit_exact() {
396        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
397        type D6 = D38<6>;
398        assert_eq!(D6::pi().to_bits(), 3_141_593_i128);
399    }
400
401    /// At SCALE = 0, pi() rounds to 3 (first fractional digit is 1, no
402    /// round-up).
403    #[test]
404    fn pi_at_scale_0_is_three() {
405        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
406        type D0 = D38<0>;
407        assert_eq!(D0::pi().to_bits(), 3_i128);
408    }
409
410    /// At SCALE = SCALE_REF (37), pi() returns exactly the raw constant.
411    #[test]
412    fn pi_at_scale_ref_is_raw_constant() {
413        type D37 = D38<37>;
414        assert_eq!(D37::pi().to_bits(), PI_RAW_S37);
415    }
416
417    /// At SCALE = SCALE_REF + 1 (38), pi() multiplies by 10, appending
418    /// one trailing zero digit. PI_RAW_S37 * 10 ≈ 3.14×10³⁸ which is
419    /// larger than i128::MAX ≈ 1.7×10³⁸, so this case overflows
420    /// `D38<38>` storage at compile time — exercising the upper end
421    /// of the rescale-up path is left to the SCALE = 37 case above.
422    #[test]
423    fn pi_at_scale_37_is_raw_constant() {
424        type D37 = D38<37>;
425        assert_eq!(D37::pi().to_bits(), PI_RAW_S37);
426    }
427
428    /// Negative-side rounding: negating pi gives the expected raw bits.
429    #[test]
430    fn neg_pi_round_trip() {
431        if !crate::rounding::DEFAULT_IS_HALF_TO_EVEN { return; }
432        let pi = D38s12::pi();
433        let neg_pi = -pi;
434        assert_eq!(neg_pi.to_bits(), -3_141_592_653_590_i128);
435    }
436
437    // (`rescale_from_ref` boundary tests removed: the rounding logic now
438    // lives in `D38::rescale` / `src/rounding.rs::apply_rounding` and is
439    // covered by the tests in those modules.)
440}