num_valid/backends/rug/
raw.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! # Arbitrary-Precision Floating-Point Raw Implementations
4//!
5//! This module provides raw trait implementations for the [`rug`](https://docs.rs/rug/) backend,
6//! enabling arbitrary-precision floating-point arithmetic with configurable precision.
7//!
8//! ## Purpose and Role
9//!
10//! The primary role of this module is to implement the core raw traits
11//! ([`RawScalarTrait`](crate::core::traits::raw::RawScalarTrait),
12//! [`RawRealTrait`](crate::core::traits::raw::RawRealTrait), etc.) for [`rug::Float`]
13//! and [`rug::Complex`]. These implementations provide the computational foundations
14//! that validated wrappers build upon.
15//!
16//! ## Precision Model
17//!
18//! Unlike the native `f64` backend with fixed 53-bit precision, the rug backend uses
19//! **const generic precision**:
20//!
21//! - Precision is specified in bits (e.g., 100, 200, 500)
22//! - All operations preserve precision of the operands
23//! - Precision mismatches are detected during validation
24//!
25//! ## MPFR Constant Optimization
26//!
27//! For mathematical constants, this module uses MPFR's precomputed constants
28//! when available, providing significant performance improvements:
29//!
30//! - [`rug::float::Constant::Pi`] - ~10x faster than computing `acos(-1)`
31//! - [`rug::float::Constant::Log2`] - ~10x faster than computing `ln(2)`
32//!
33//! ## Memory and Performance Characteristics
34//!
35//! - `rug::Float` and `rug::Complex` are heap-allocated, non-`Copy` types
36//! - All operations support reference-based variants to minimize cloning
37//! - Memory usage scales with precision (approximately `precision/8` bytes per value)
38
39use crate::{
40    core::{
41        errors::{
42            ErrorsRawRealToInteger, ErrorsTryFromf64, ErrorsValidationRawComplex,
43            ErrorsValidationRawReal, capture_backtrace,
44        },
45        traits::{
46            raw::{
47                RawComplexTrait, RawRealTrait, RawScalarHyperbolic, RawScalarPow, RawScalarTrait,
48                RawScalarTrigonometric,
49            },
50            validation::{FpChecks, ValidationPolicyReal},
51        },
52    },
53    functions::{Conjugate, NegAssign, Rounding, Sign},
54};
55use duplicate::duplicate_item;
56use rug::{
57    float::Constant as MpfrConstant,
58    ops::{CompleteRound, Pow},
59};
60use std::{
61    cmp::Ordering,
62    hash::{Hash, Hasher},
63    num::FpCategory,
64    ops::Neg,
65};
66
67#[duplicate_item(
68    T;
69    [rug::Float];
70    [rug::Complex];
71)]
72impl RawScalarTrigonometric for T {
73    #[duplicate_item(
74        unchecked_method method;
75        [unchecked_sin]  [sin];
76        [unchecked_asin] [asin];
77        [unchecked_cos]  [cos];
78        [unchecked_acos] [acos];
79        [unchecked_tan]  [tan];
80        [unchecked_atan] [atan];)]
81    #[inline(always)]
82    fn unchecked_method(self) -> Self {
83        T::method(self)
84    }
85}
86
87#[duplicate_item(
88    T;
89    [rug::Float];
90    [rug::Complex];
91)]
92impl RawScalarHyperbolic for T {
93    #[duplicate_item(
94        unchecked_method  method;
95        [unchecked_sinh]  [sinh];
96        [unchecked_asinh] [asinh];
97        [unchecked_cosh]  [cosh];
98        [unchecked_acosh] [acosh];
99        [unchecked_tanh]  [tanh];
100        [unchecked_atanh] [atanh];)]
101    #[inline(always)]
102    fn unchecked_method(self) -> Self {
103        T::method(self)
104    }
105}
106
107#[duplicate_item(
108    T;
109    [rug::Float];
110    [rug::Complex];
111)]
112impl RawScalarPow for T {
113    #[duplicate_item(
114        unchecked_method               exponent_type;
115        [unchecked_pow_exponent_i8]    [i8];
116        [unchecked_pow_exponent_i16]   [i16];
117        [unchecked_pow_exponent_i32]   [i32];
118        [unchecked_pow_exponent_i64]   [i64];
119        [unchecked_pow_exponent_i128]  [i128];
120        [unchecked_pow_exponent_isize] [isize];
121        [unchecked_pow_exponent_u8]    [u8];
122        [unchecked_pow_exponent_u16]   [u16];
123        [unchecked_pow_exponent_u32]   [u32];
124        [unchecked_pow_exponent_u64]   [u64];
125        [unchecked_pow_exponent_u128]  [u128];
126        [unchecked_pow_exponent_usize] [usize];
127    )]
128    #[inline(always)]
129    fn unchecked_method(self, exponent: &exponent_type) -> Self {
130        T::pow(self, exponent)
131    }
132}
133
134impl RawScalarTrait for rug::Float {
135    type ValidationErrors = ErrorsValidationRawReal<rug::Float>;
136
137    fn raw_zero(precision: u32) -> Self {
138        rug::Float::with_val(precision, 0.)
139    }
140
141    fn is_zero(&self) -> bool {
142        rug::Float::is_zero(self)
143    }
144
145    fn raw_one(precision: u32) -> Self {
146        rug::Float::with_val(precision, 1.)
147    }
148
149    #[duplicate_item(
150        unchecked_method       method;
151        [unchecked_reciprocal] [recip];
152        [unchecked_exp]        [exp];
153        [unchecked_sqrt]       [sqrt];
154        [unchecked_ln]         [ln];
155        [unchecked_log2]       [log2];
156        [unchecked_log10]      [log10];
157    )]
158    #[inline(always)]
159    fn unchecked_method(self) -> Self {
160        rug::Float::method(self)
161    }
162
163    /// Multiplies and adds in one fused operation, rounding to the nearest with only one rounding error.
164    ///
165    /// `a.mul_add(b, c)` produces a result like `a * &b + &c`.
166    #[inline(always)]
167    fn unchecked_mul_add(self, b: &Self, c: &Self) -> Self {
168        rug::Float::mul_add(self, b, c)
169    }
170
171    #[inline(always)]
172    fn compute_hash<H: Hasher>(&self, state: &mut H) {
173        debug_assert!(
174            self.is_finite(),
175            "Hashing a non-finite rug::Float value (i.e., NaN or Infinity) may lead to inconsistent results."
176        );
177        // Always include precision in the hash
178        self.prec().hash(state);
179
180        // Handle signed zeros by normalizing to positive zero
181        if self.is_zero() {
182            // Ensure that -0.0 and 0.0 hash to the same value
183            rug::Float::with_val(self.prec(), 0.0)
184                .to_string_radix(10, None)
185                .hash(state);
186        } else {
187            // For non-zero values, create a deterministic string representation
188            // Use enough decimal places to capture the full precision
189            self.to_string_radix(10, None).hash(state)
190        }
191    }
192}
193
194impl RawRealTrait for rug::Float {
195    type RawComplex = rug::Complex;
196
197    #[inline(always)]
198    fn unchecked_abs(self) -> rug::Float {
199        rug::Float::abs(self)
200    }
201
202    #[inline(always)]
203    fn unchecked_atan2(self, denominator: &Self) -> Self {
204        rug::Float::atan2(self, denominator)
205    }
206
207    #[inline(always)]
208    fn unchecked_pow_exponent_real(self, exponent: &Self) -> Self {
209        rug::Float::pow(self, exponent)
210    }
211
212    #[inline(always)]
213    fn unchecked_hypot(self, other: &Self) -> Self {
214        rug::Float::hypot(self, other)
215    }
216
217    #[inline(always)]
218    fn unchecked_ln_1p(self) -> Self {
219        rug::Float::ln_1p(self)
220    }
221
222    #[inline(always)]
223    fn unchecked_exp_m1(self) -> Self {
224        rug::Float::exp_m1(self)
225    }
226
227    /// Multiplies two pairs and adds them in one fused operation, rounding to the nearest with only one rounding error.
228    /// `a.unchecked_mul_add_mul_mut(&b, &c, &d)` produces a result like `&a * &b + &c * &d`, but stores the result in `a` using its precision.
229    #[inline(always)]
230    fn unchecked_mul_add_mul_mut(&mut self, mul: &Self, add_mul1: &Self, add_mul2: &Self) {
231        self.mul_add_mul_mut(mul, add_mul1, add_mul2);
232    }
233
234    /// Multiplies two pairs and subtracts them in one fused operation, rounding to the nearest with only one rounding error.
235    /// `a.unchecked_mul_sub_mul_mut(&b, &c, &d)` produces a result like `&a * &b - &c * &d`, but stores the result in `a` using its precision.
236    #[inline(always)]
237    fn unchecked_mul_sub_mul_mut(&mut self, mul: &Self, sub_mul1: &Self, sub_mul2: &Self) {
238        self.mul_sub_mul_mut(mul, sub_mul1, sub_mul2);
239    }
240
241    #[inline(always)]
242    fn raw_total_cmp(&self, other: &Self) -> Ordering {
243        rug::Float::total_cmp(self, other)
244    }
245
246    /// Clamps the value within the specified bounds.
247    #[inline(always)]
248    fn raw_clamp(self, min: &Self, max: &Self) -> Self {
249        rug::Float::clamp(self, min, max)
250    }
251
252    #[inline(always)]
253    fn raw_classify(&self) -> FpCategory {
254        rug::Float::classify(self)
255    }
256
257    #[inline(always)]
258    fn raw_two(precision: u32) -> Self {
259        rug::Float::with_val(precision, 2.)
260    }
261
262    #[inline(always)]
263    fn raw_one_div_2(precision: u32) -> Self {
264        rug::Float::with_val(precision, 0.5)
265    }
266
267    #[inline(always)]
268    fn raw_pi(precision: u32) -> Self {
269        // Use MPFR's optimized precomputed π constant (10x faster than acos(-1))
270        rug::Float::with_val(precision, MpfrConstant::Pi)
271    }
272
273    #[inline(always)]
274    fn raw_two_pi(precision: u32) -> Self {
275        rug::Float::with_val(precision, MpfrConstant::Pi) * 2
276    }
277
278    #[inline(always)]
279    fn raw_pi_div_2(precision: u32) -> Self {
280        rug::Float::with_val(precision, MpfrConstant::Pi) / 2
281    }
282
283    #[inline(always)]
284    fn raw_max_finite(precision: u32) -> Self {
285        // Create a Float with the desired precision
286        // let f = rug::Float::new(PRECISION);
287
288        // Fill the significand with all 1s: 1 - 2^(-precision)
289        // This gives you the largest significand before it rolls over
290        let one = rug::Float::with_val(precision, 1);
291        let eps = rug::Float::with_val(precision, rug::Float::u_pow_u(2, precision)).recip();
292        let significand = one - &eps;
293
294        // Scale it to the maximum exponent (subtract 1 to avoid overflow to infinity)
295        let max_exp = rug::float::exp_max() - 1;
296        //        println!("max_exp = {max_exp}");
297        significand * rug::Float::with_val(precision, rug::Float::u_pow_u(2, max_exp as u32))
298    }
299
300    #[inline(always)]
301    fn raw_min_finite(precision: u32) -> Self {
302        Self::raw_max_finite(precision).neg()
303    }
304
305    #[inline(always)]
306    fn raw_epsilon(precision: u32) -> Self {
307        rug::Float::u_pow_u(2, precision - 1)
308            .complete(precision)
309            .recip()
310    }
311
312    #[inline(always)]
313    fn raw_ln_2(precision: u32) -> Self {
314        // Use MPFR's optimized precomputed ln(2) constant
315        rug::Float::with_val(precision, MpfrConstant::Log2)
316    }
317
318    #[inline(always)]
319    fn raw_ln_10(precision: u32) -> Self {
320        // ln(10) = ln(2) * log₂(10)
321        // More efficient than computing ln(10) directly
322        let ln2 = rug::Float::with_val(precision, MpfrConstant::Log2);
323        let log2_10 = rug::Float::with_val(precision, 10).log2();
324        ln2 * log2_10
325    }
326
327    #[inline(always)]
328    fn raw_log10_2(precision: u32) -> Self {
329        rug::Float::with_val(precision, 2.).log10()
330    }
331
332    #[inline(always)]
333    fn raw_log2_10(precision: u32) -> Self {
334        rug::Float::with_val(precision, 10.).log2()
335    }
336
337    #[inline(always)]
338    fn raw_log2_e(precision: u32) -> Self {
339        // log₂(e) = 1/ln(2)
340        // More efficient than computing e then taking log₂
341        rug::Float::with_val(precision, MpfrConstant::Log2).recip()
342    }
343
344    #[inline(always)]
345    fn raw_log10_e(precision: u32) -> Self {
346        // log₁₀(e) = 1/ln(10) = 1/(ln(2) * log₂(10))
347        let ln2 = rug::Float::with_val(precision, MpfrConstant::Log2);
348        let log2_10 = rug::Float::with_val(precision, 10).log2();
349        (ln2 * log2_10).recip()
350    }
351
352    #[inline(always)]
353    fn raw_e(precision: u32) -> Self {
354        rug::Float::with_val(precision, 1.).exp()
355    }
356
357    #[inline(always)]
358    fn try_new_raw_real_from_f64<RealPolicy: ValidationPolicyReal<Value = Self>>(
359        value: f64,
360    ) -> Result<Self, ErrorsTryFromf64<Self>> {
361        let precision = RealPolicy::PRECISION;
362
363        // f64 has 53 bits of precision (52 explicit + 1 implicit)
364        const F64_PRECISION: u32 = 53;
365
366        // If target precision is less than f64, we would lose information
367        if precision < F64_PRECISION {
368            // Create a placeholder rug::Float to satisfy the error type
369            let placeholder = rug::Float::with_val(precision, value);
370            return Err(ErrorsTryFromf64::NonRepresentableExactly {
371                value_in: value,
372                value_converted_from_f64: placeholder,
373                precision,
374                backtrace: capture_backtrace(),
375            });
376        }
377
378        // Convert f64 to rug::Float at target precision
379        // Note: All finite f64 values are exactly representable at precision ≥ 53
380        let value_rug = rug::Float::with_val(precision, value);
381
382        // Validate the converted value (checks for NaN, Inf, subnormal)
383        let validated =
384            RealPolicy::validate(value_rug).map_err(|e| ErrorsTryFromf64::Output { source: e })?;
385
386        Ok(validated)
387    }
388
389    #[inline(always)]
390    fn precision(&self) -> u32 {
391        rug::Float::prec(self)
392    }
393
394    #[inline(always)]
395    fn truncate_to_usize(self) -> Result<usize, ErrorsRawRealToInteger<rug::Float, usize>> {
396        if !self.is_finite() {
397            return Err(ErrorsRawRealToInteger::NotFinite {
398                value: self,
399                backtrace: capture_backtrace(),
400            });
401        }
402
403        let truncation = self.clone().trunc();
404
405        let out_of_range = || ErrorsRawRealToInteger::OutOfRange {
406            value: self,
407            min: usize::MIN,
408            max: usize::MAX,
409            backtrace: capture_backtrace(),
410        };
411
412        let truncation_as_rug_integer = truncation.to_integer().ok_or_else(out_of_range.clone())?;
413
414        truncation_as_rug_integer
415            .to_usize()
416            .ok_or_else(out_of_range)
417    }
418}
419
420impl RawScalarTrait for rug::Complex {
421    type ValidationErrors = ErrorsValidationRawComplex<ErrorsValidationRawReal<rug::Float>>;
422
423    fn raw_zero(precision: u32) -> Self {
424        rug::Complex::with_val(precision, (0., 0.))
425    }
426
427    fn is_zero(&self) -> bool {
428        rug::Complex::is_zero(self)
429    }
430
431    fn raw_one(precision: u32) -> Self {
432        rug::Complex::with_val(precision, (1., 0.))
433    }
434
435    #[duplicate_item(
436        unchecked_method       method;
437        [unchecked_reciprocal] [recip];
438        [unchecked_exp]        [exp];
439        [unchecked_sqrt]       [sqrt];
440        [unchecked_ln]         [ln];
441        [unchecked_log10]      [log10];
442    )]
443    #[inline(always)]
444    fn unchecked_method(self) -> Self {
445        rug::Complex::method(self)
446    }
447
448    #[inline(always)]
449    fn unchecked_log2(self) -> Self {
450        let ln_2 = rug::Float::with_val(self.real().prec(), 2.).ln();
451        rug::Complex::ln(self) / ln_2
452    }
453
454    /// Multiplies and adds in one fused operation, rounding to the nearest with only one rounding error.
455    ///
456    /// `a.mul_add(b, c)` produces a result like `a * &b + &c`.
457    #[inline(always)]
458    fn unchecked_mul_add(self, b: &Self, c: &Self) -> Self {
459        rug::Complex::mul_add(self, b, c)
460    }
461
462    fn compute_hash<H: Hasher>(&self, state: &mut H) {
463        self.raw_real_part().compute_hash(state);
464        self.raw_imag_part().compute_hash(state);
465    }
466}
467
468impl Conjugate for rug::Complex {
469    #[inline(always)]
470    fn conjugate(self) -> Self {
471        rug::Complex::conj(self)
472    }
473}
474
475impl RawComplexTrait for rug::Complex {
476    type RawReal = rug::Float;
477
478    fn new_unchecked_raw_complex(real: rug::Float, imag: rug::Float) -> Self {
479        debug_assert_eq!(
480            real.prec(),
481            imag.prec(),
482            "Different precision between real and imaginary part!"
483        );
484        rug::Complex::with_val(real.prec(), (real, imag))
485    }
486
487    /// Returns a mutable reference to the real part of the complex number.
488    fn mut_raw_real_part(&mut self) -> &mut rug::Float {
489        self.mut_real()
490    }
491
492    /// Returns a mutable reference to the imaginary part of the complex number.
493    fn mut_raw_imag_part(&mut self) -> &mut rug::Float {
494        self.mut_imag()
495    }
496
497    #[inline(always)]
498    fn unchecked_abs(self) -> rug::Float {
499        rug::Complex::abs(self).into_real_imag().0
500    }
501
502    #[inline(always)]
503    fn raw_real_part(&self) -> &rug::Float {
504        self.real()
505    }
506
507    #[inline(always)]
508    fn raw_imag_part(&self) -> &rug::Float {
509        self.imag()
510    }
511
512    #[inline(always)]
513    fn unchecked_arg(self) -> rug::Float {
514        rug::Complex::arg(self).into_real_imag().0
515    }
516
517    #[inline(always)]
518    fn unchecked_pow_exponent_real(self, exponent: &rug::Float) -> Self {
519        rug::Complex::pow(self, exponent)
520    }
521}
522
523impl FpChecks for rug::Float {
524    fn is_finite(&self) -> bool {
525        rug::Float::is_finite(self)
526    }
527
528    fn is_infinite(&self) -> bool {
529        rug::Float::is_infinite(self)
530    }
531
532    fn is_nan(&self) -> bool {
533        rug::Float::is_nan(self)
534    }
535    fn is_normal(&self) -> bool {
536        rug::Float::is_normal(self)
537    }
538}
539
540impl FpChecks for rug::Complex {
541    /// Returns `true` if the real and imaginary parts of `self` are neither infinite nor NaN.
542    #[inline(always)]
543    fn is_finite(&self) -> bool {
544        self.real().is_finite() && self.imag().is_finite()
545    }
546
547    /// Returns `true` if the real or the imaginary part of `self` are positive infinity or negative infinity.
548    #[inline(always)]
549    fn is_infinite(&self) -> bool {
550        !self.is_nan() && (self.real().is_infinite() || self.imag().is_infinite())
551    }
552
553    /// Returns `true` if the real or the imaginary part of `self` are NaN.
554    #[inline(always)]
555    fn is_nan(&self) -> bool {
556        self.real().is_nan() || self.imag().is_nan()
557    }
558
559    /// Returns `true` if the real and the imaginary part of `self` are *normal* (i.e. neither zero, infinite, subnormal, or NaN).
560    #[inline(always)]
561    fn is_normal(&self) -> bool {
562        self.real().is_normal() && self.imag().is_normal()
563    }
564}
565
566//------------------------------------------------------------------------------------------------
567#[duplicate_item(
568    T;
569    [rug::Float];
570    [rug::Complex];
571)]
572/// Compound negation and assignment.
573impl NegAssign for T {
574    /// Performs the negation of `self`.
575    fn neg_assign(&mut self) {
576        <T as rug::ops::NegAssign>::neg_assign(self);
577    }
578}
579//------------------------------------------------------------------------------------------------
580
581impl Sign for rug::Float {
582    /// Returns a number with the magnitude of `self` and the sign of `sign`.
583    #[inline(always)]
584    fn kernel_copysign(self, sign: &Self) -> Self {
585        self.copysign(sign)
586    }
587
588    /// Returns `true` if the value is negative, −0 or NaN with a negative sign.
589    #[inline(always)]
590    fn kernel_is_sign_negative(&self) -> bool {
591        self.is_sign_negative()
592    }
593
594    /// Returns `true` if the value is positive, +0 or NaN with a positive sign.
595    #[inline(always)]
596    fn kernel_is_sign_positive(&self) -> bool {
597        self.is_sign_positive()
598    }
599
600    /// Returns the signum of the number.
601    ///
602    /// This method relies on `rug::Float::signum()`.
603    /// The behavior for special values is as follows:
604    /// - If the number is positive and not zero, returns `1.0`.
605    /// - If the number is negative and not zero, returns `-1.0`.
606    /// - If the number is zero, `rug::Float::signum()` returns `1.0` (representing positive zero).
607    #[inline(always)]
608    fn kernel_signum(self) -> Self {
609        self.signum()
610    }
611}
612
613impl Rounding for rug::Float {
614    /// Returns the smallest integer greater than or equal to `self`.
615    #[inline(always)]
616    fn kernel_ceil(self) -> Self {
617        self.ceil()
618    }
619
620    /// Returns the largest integer smaller than or equal to `self`.
621    #[inline(always)]
622    fn kernel_floor(self) -> Self {
623        self.floor()
624    }
625
626    /// Returns the fractional part of `self`.
627    #[inline(always)]
628    fn kernel_fract(self) -> Self {
629        self.fract()
630    }
631
632    /// Rounds `self` to the nearest integer, rounding half-way cases away from zero.
633    #[inline(always)]
634    fn kernel_round(self) -> Self {
635        self.round()
636    }
637
638    /// Returns the nearest integer to a number. Rounds half-way cases to the number with an even least significant digit.
639    ///
640    /// This function always returns the precise result.
641    ///
642    /// # Examples
643    /// ```
644    /// use num_valid::{RealScalar, RealRugStrictFinite, functions::Rounding};
645    ///
646    /// const PRECISION: u32 = 100;
647    ///
648    /// let f = RealRugStrictFinite::<PRECISION>::try_from_f64(3.3).unwrap();
649    /// let g = RealRugStrictFinite::<PRECISION>::try_from_f64(-3.3).unwrap();
650    /// let h = RealRugStrictFinite::<PRECISION>::try_from_f64(3.5).unwrap();
651    /// let i = RealRugStrictFinite::<PRECISION>::try_from_f64(-4.5).unwrap();
652    ///
653    /// assert_eq!(f.kernel_round_ties_even(), RealRugStrictFinite::<PRECISION>::try_from_f64(3.).unwrap());
654    /// assert_eq!(g.kernel_round_ties_even(), RealRugStrictFinite::<PRECISION>::try_from_f64(-3.).unwrap());
655    /// assert_eq!(h.kernel_round_ties_even(), RealRugStrictFinite::<PRECISION>::try_from_f64(4.).unwrap());
656    /// assert_eq!(i.kernel_round_ties_even(), RealRugStrictFinite::<PRECISION>::try_from_f64(-4.).unwrap());
657    /// ```
658    #[inline(always)]
659    fn kernel_round_ties_even(self) -> Self {
660        self.round_even()
661    }
662
663    /// Returns the integer part of `self`. This means that non-integer numbers are always truncated towards zero.
664    ///    
665    /// # Examples
666    /// ```
667    /// use num_valid::{RealScalar, RealRugStrictFinite, functions::Rounding};
668    ///
669    /// const PRECISION: u32 = 100;
670    ///
671    /// let f = RealRugStrictFinite::<PRECISION>::try_from_f64(3.7).unwrap();
672    /// let g = RealRugStrictFinite::<PRECISION>::try_from_f64(3.).unwrap();
673    /// let h = RealRugStrictFinite::<PRECISION>::try_from_f64(-3.7).unwrap();
674    ///
675    /// assert_eq!(f.kernel_trunc(), RealRugStrictFinite::<PRECISION>::try_from_f64(3.).unwrap());
676    /// assert_eq!(g.kernel_trunc(), RealRugStrictFinite::<PRECISION>::try_from_f64(3.).unwrap());
677    /// assert_eq!(h.kernel_trunc(), RealRugStrictFinite::<PRECISION>::try_from_f64(-3.).unwrap());
678    /// ```
679    #[inline(always)]
680    fn kernel_trunc(self) -> Self {
681        self.trunc()
682    }
683}