stv_rs/arithmetic/
fixed.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Module for fixed-point arithmetic, defined in terms of *decimal* places.
16//! For now, it only implements 9 decimal places (i.e. with a factor `10^-9`).
17//! This implementation is backed by [`i64`], and will panic in case of overflow
18//! if the `checked_i64` feature is enabled.
19
20use super::{Integer, IntegerRef, Rational, RationalRef};
21use num::traits::{One, Zero};
22use num::Integer as NumInteger;
23use std::fmt::{Debug, Display};
24use std::iter::{Product, Sum};
25use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
26
27/// An integer wrapping a [`i64`], performing arithmetic overflow checks if the
28/// `checked_i64` feature is enabled.
29#[derive(Clone, Copy, PartialEq, Eq, PartialOrd)]
30pub struct Integer64(i64);
31
32impl Debug for Integer64 {
33    #[inline(always)]
34    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
35        write!(f, "{}", self.0)
36    }
37}
38
39#[cfg(test)]
40impl Display for Integer64 {
41    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
42        write!(f, "{}", self.0)
43    }
44}
45
46impl Zero for Integer64 {
47    #[inline(always)]
48    fn zero() -> Self {
49        Integer64(i64::zero())
50    }
51    #[inline(always)]
52    fn is_zero(&self) -> bool {
53        self.0.is_zero()
54    }
55}
56impl One for Integer64 {
57    #[inline(always)]
58    fn one() -> Self {
59        Integer64(1)
60    }
61}
62
63impl Add for Integer64 {
64    type Output = Self;
65    #[inline(always)]
66    fn add(self, rhs: Self) -> Self {
67        #[cfg(feature = "checked_i64")]
68        return Integer64(self.0.checked_add(rhs.0).unwrap());
69        #[cfg(not(feature = "checked_i64"))]
70        return Integer64(self.0 + rhs.0);
71    }
72}
73impl Sub for Integer64 {
74    type Output = Self;
75    #[inline(always)]
76    fn sub(self, rhs: Self) -> Self {
77        #[cfg(feature = "checked_i64")]
78        return Integer64(self.0.checked_sub(rhs.0).unwrap());
79        #[cfg(not(feature = "checked_i64"))]
80        return Integer64(self.0 - rhs.0);
81    }
82}
83impl Mul for Integer64 {
84    type Output = Self;
85    #[inline(always)]
86    fn mul(self, rhs: Self) -> Self {
87        #[cfg(feature = "checked_i64")]
88        return Integer64(self.0.checked_mul(rhs.0).unwrap());
89        #[cfg(not(feature = "checked_i64"))]
90        return Integer64(self.0 * rhs.0);
91    }
92}
93
94impl Add<&'_ Integer64> for &'_ Integer64 {
95    type Output = Integer64;
96    #[inline(always)]
97    fn add(self, rhs: &'_ Integer64) -> Integer64 {
98        *self + *rhs
99    }
100}
101impl Sub<&'_ Integer64> for &'_ Integer64 {
102    type Output = Integer64;
103    #[inline(always)]
104    fn sub(self, rhs: &'_ Integer64) -> Integer64 {
105        *self - *rhs
106    }
107}
108impl Mul<&'_ Integer64> for &'_ Integer64 {
109    type Output = Integer64;
110    #[inline(always)]
111    fn mul(self, rhs: &'_ Integer64) -> Integer64 {
112        *self * *rhs
113    }
114}
115
116impl Product for Integer64 {
117    #[inline(always)]
118    fn product<I>(iter: I) -> Self
119    where
120        I: Iterator<Item = Self>,
121    {
122        iter.fold(Self::one(), |acc, x| acc * x)
123    }
124}
125
126impl Integer for Integer64 {
127    #[inline(always)]
128    fn from_usize(i: usize) -> Self {
129        #[cfg(feature = "checked_i64")]
130        return Integer64(i.try_into().unwrap());
131        #[cfg(not(feature = "checked_i64"))]
132        return Integer64(i as i64);
133    }
134
135    #[cfg(test)]
136    fn get_positive_test_values() -> Vec<Self> {
137        let mut result = Vec::new();
138        for i in 0..=30 {
139            result.push(Integer64(1 << i));
140            result.push(Integer64(0x7FFF_FFFF ^ (1 << i)));
141        }
142        result
143    }
144}
145
146impl IntegerRef<Integer64> for &Integer64 {}
147
148/// A fixed-point decimal arithmetic for 9 decimal places. This type represents
149/// a number `x` by the integer `x * 10^9`, backed by a [`i64`].
150#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
151pub struct FixedDecimal9(i64);
152
153impl FixedDecimal9 {
154    const FACTOR: i64 = 1_000_000_000;
155    const FACTOR_I128: i128 = Self::FACTOR as i128;
156}
157
158#[cfg(test)]
159impl FixedDecimal9 {
160    #[inline(always)]
161    pub(crate) fn new(x: i64) -> Self {
162        FixedDecimal9(x)
163    }
164
165    fn from_i64(x: i64) -> Self {
166        FixedDecimal9::from_int(Integer64(x))
167    }
168}
169
170impl Display for FixedDecimal9 {
171    #[inline(always)]
172    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
173        let sign = if self.0 < 0 { "-" } else { "" };
174        let (i, rem) = self.0.abs().div_rem(&Self::FACTOR);
175        write!(f, "{sign}{i}.{rem:09}")
176    }
177}
178
179impl Zero for FixedDecimal9 {
180    #[inline(always)]
181    fn zero() -> Self {
182        FixedDecimal9(i64::zero())
183    }
184    #[inline(always)]
185    fn is_zero(&self) -> bool {
186        self.0.is_zero()
187    }
188}
189impl One for FixedDecimal9 {
190    #[inline(always)]
191    fn one() -> Self {
192        FixedDecimal9(Self::FACTOR)
193    }
194}
195
196impl Add for FixedDecimal9 {
197    type Output = Self;
198    #[inline(always)]
199    fn add(self, rhs: Self) -> Self {
200        #[cfg(feature = "checked_i64")]
201        return FixedDecimal9(self.0.checked_add(rhs.0).unwrap());
202        #[cfg(not(feature = "checked_i64"))]
203        return FixedDecimal9(self.0 + rhs.0);
204    }
205}
206impl Sub for FixedDecimal9 {
207    type Output = Self;
208    #[inline(always)]
209    fn sub(self, rhs: Self) -> Self {
210        #[cfg(feature = "checked_i64")]
211        return FixedDecimal9(self.0.checked_sub(rhs.0).unwrap());
212        #[cfg(not(feature = "checked_i64"))]
213        return FixedDecimal9(self.0 - rhs.0);
214    }
215}
216impl Mul for FixedDecimal9 {
217    type Output = Self;
218    #[inline(always)]
219    fn mul(self, rhs: Self) -> Self {
220        FixedDecimal9(match self.0.checked_mul(rhs.0) {
221            Some(product) => product / Self::FACTOR,
222            None => {
223                let result: i128 = (self.0 as i128 * rhs.0 as i128) / Self::FACTOR_I128;
224                #[cfg(feature = "checked_i64")]
225                let result: i64 = result.try_into().unwrap();
226                #[cfg(not(feature = "checked_i64"))]
227                let result: i64 = result as i64;
228                result
229            }
230        })
231    }
232}
233impl Mul<Integer64> for FixedDecimal9 {
234    type Output = Self;
235    #[inline(always)]
236    fn mul(self, rhs: Integer64) -> Self {
237        #[cfg(feature = "checked_i64")]
238        return FixedDecimal9(self.0.checked_mul(rhs.0).unwrap());
239        #[cfg(not(feature = "checked_i64"))]
240        return FixedDecimal9(self.0 * rhs.0);
241    }
242}
243impl Div<Integer64> for FixedDecimal9 {
244    type Output = Self;
245    #[inline(always)]
246    fn div(self, rhs: Integer64) -> Self {
247        #[cfg(feature = "checked_i64")]
248        return FixedDecimal9(self.0.checked_div(rhs.0).unwrap());
249        #[cfg(not(feature = "checked_i64"))]
250        return FixedDecimal9(self.0 / rhs.0);
251    }
252}
253
254impl Add<&'_ Self> for FixedDecimal9 {
255    type Output = Self;
256    #[inline(always)]
257    fn add(self, rhs: &'_ Self) -> Self {
258        self + *rhs
259    }
260}
261impl Sub<&'_ Self> for FixedDecimal9 {
262    type Output = Self;
263    #[inline(always)]
264    fn sub(self, rhs: &'_ Self) -> Self {
265        self - *rhs
266    }
267}
268impl Mul<&'_ Self> for FixedDecimal9 {
269    type Output = Self;
270    #[inline(always)]
271    fn mul(self, rhs: &'_ Self) -> Self {
272        self * *rhs
273    }
274}
275
276impl Add<&'_ FixedDecimal9> for &'_ FixedDecimal9 {
277    type Output = FixedDecimal9;
278    #[inline(always)]
279    fn add(self, rhs: &'_ FixedDecimal9) -> FixedDecimal9 {
280        *self + *rhs
281    }
282}
283impl Sub<&'_ FixedDecimal9> for &'_ FixedDecimal9 {
284    type Output = FixedDecimal9;
285    #[inline(always)]
286    fn sub(self, rhs: &'_ FixedDecimal9) -> FixedDecimal9 {
287        *self - *rhs
288    }
289}
290impl Mul<&'_ FixedDecimal9> for &'_ FixedDecimal9 {
291    type Output = FixedDecimal9;
292    #[inline(always)]
293    fn mul(self, rhs: &'_ FixedDecimal9) -> FixedDecimal9 {
294        *self * *rhs
295    }
296}
297impl Mul<&'_ Integer64> for &'_ FixedDecimal9 {
298    type Output = FixedDecimal9;
299    #[inline(always)]
300    fn mul(self, rhs: &'_ Integer64) -> FixedDecimal9 {
301        *self * *rhs
302    }
303}
304impl Div<&'_ Integer64> for &'_ FixedDecimal9 {
305    type Output = FixedDecimal9;
306    #[inline(always)]
307    fn div(self, rhs: &'_ Integer64) -> FixedDecimal9 {
308        *self / *rhs
309    }
310}
311
312impl AddAssign for FixedDecimal9 {
313    #[inline(always)]
314    fn add_assign(&mut self, rhs: Self) {
315        *self = *self + rhs
316    }
317}
318impl SubAssign for FixedDecimal9 {
319    #[inline(always)]
320    fn sub_assign(&mut self, rhs: Self) {
321        *self = *self - rhs
322    }
323}
324impl MulAssign for FixedDecimal9 {
325    #[inline(always)]
326    fn mul_assign(&mut self, rhs: Self) {
327        *self = *self * rhs
328    }
329}
330
331impl AddAssign<&'_ Self> for FixedDecimal9 {
332    #[inline(always)]
333    fn add_assign(&mut self, rhs: &'_ Self) {
334        *self = *self + *rhs
335    }
336}
337impl SubAssign<&'_ Self> for FixedDecimal9 {
338    #[inline(always)]
339    fn sub_assign(&mut self, rhs: &'_ Self) {
340        *self = *self - *rhs
341    }
342}
343impl MulAssign<&'_ Self> for FixedDecimal9 {
344    #[inline(always)]
345    fn mul_assign(&mut self, rhs: &'_ Self) {
346        *self = *self * *rhs
347    }
348}
349impl DivAssign<&'_ Integer64> for FixedDecimal9 {
350    #[inline(always)]
351    fn div_assign(&mut self, rhs: &'_ Integer64) {
352        *self = *self / *rhs
353    }
354}
355
356impl Sum for FixedDecimal9 {
357    #[inline(always)]
358    fn sum<I>(iter: I) -> Self
359    where
360        I: Iterator<Item = Self>,
361    {
362        FixedDecimal9(iter.map(|item| item.0).sum())
363    }
364}
365
366impl Product for FixedDecimal9 {
367    #[inline(always)]
368    fn product<I>(iter: I) -> Self
369    where
370        I: Iterator<Item = Self>,
371    {
372        iter.fold(Self::one(), |acc, x| acc * x)
373    }
374}
375
376impl<'a> Sum<&'a Self> for FixedDecimal9 {
377    #[inline(always)]
378    fn sum<I>(iter: I) -> Self
379    where
380        I: Iterator<Item = &'a Self>,
381    {
382        FixedDecimal9(iter.map(|item| &item.0).sum())
383    }
384}
385
386impl RationalRef<&Integer64, FixedDecimal9> for &FixedDecimal9 {}
387
388impl Rational<Integer64> for FixedDecimal9 {
389    #[inline(always)]
390    fn from_int(i: Integer64) -> Self {
391        #[cfg(feature = "checked_i64")]
392        return FixedDecimal9(i.0.checked_mul(Self::FACTOR).unwrap());
393        #[cfg(not(feature = "checked_i64"))]
394        return FixedDecimal9(i.0 * Self::FACTOR);
395    }
396
397    #[inline(always)]
398    fn ratio_i(num: Integer64, denom: Integer64) -> Self {
399        FixedDecimal9(match num.0.checked_mul(Self::FACTOR) {
400            Some(product) => {
401                #[cfg(feature = "checked_i64")]
402                let result: i64 = product.checked_div(denom.0).unwrap();
403                #[cfg(not(feature = "checked_i64"))]
404                let result: i64 = product / denom.0;
405                result
406            }
407            None => {
408                let product: i128 = num.0 as i128 * Self::FACTOR_I128;
409                #[cfg(feature = "checked_i64")]
410                let result: i64 = product
411                    .checked_div(denom.0 as i128)
412                    .unwrap()
413                    .try_into()
414                    .unwrap();
415                #[cfg(not(feature = "checked_i64"))]
416                let result: i64 = (product / denom.0 as i128) as i64;
417                result
418            }
419        })
420    }
421
422    #[inline(always)]
423    fn to_f64(&self) -> f64 {
424        self.0 as f64 / Self::FACTOR as f64
425    }
426
427    #[inline(always)]
428    fn epsilon() -> Self {
429        FixedDecimal9(1)
430    }
431
432    #[inline(always)]
433    fn is_exact() -> bool {
434        false
435    }
436
437    #[inline(always)]
438    fn description() -> &'static str {
439        "fixed-point decimal arithmetic (9 places)"
440    }
441
442    #[inline(always)]
443    fn mul_up(&self, rhs: &Self) -> Self {
444        FixedDecimal9(
445            match self
446                .0
447                .checked_mul(rhs.0)
448                .and_then(|product| product.checked_add(Self::FACTOR - 1))
449            {
450                Some(value) => value / Self::FACTOR,
451                None => {
452                    let product: i128 = self.0 as i128 * rhs.0 as i128;
453                    #[cfg(feature = "checked_i64")]
454                    let result: i64 = (product.checked_add(Self::FACTOR_I128 - 1).unwrap()
455                        / Self::FACTOR_I128)
456                        .try_into()
457                        .unwrap();
458                    #[cfg(not(feature = "checked_i64"))]
459                    let result: i64 =
460                        ((product + Self::FACTOR_I128 - 1) / Self::FACTOR_I128) as i64;
461                    result
462                }
463            },
464        )
465    }
466
467    #[inline(always)]
468    fn div_up_as_keep_factor(&self, rhs: &Self) -> Self {
469        FixedDecimal9(
470            match self
471                .0
472                .checked_mul(Self::FACTOR)
473                .and_then(|product| product.checked_add(rhs.0 - 1))
474            {
475                Some(value) => {
476                    #[cfg(feature = "checked_i64")]
477                    let result: i64 = value.checked_div(rhs.0).unwrap();
478                    #[cfg(not(feature = "checked_i64"))]
479                    let result: i64 = value / rhs.0;
480                    result
481                }
482                None => {
483                    let value: i128 = self.0 as i128 * Self::FACTOR_I128 + rhs.0 as i128 - 1;
484                    #[cfg(feature = "checked_i64")]
485                    let result: i64 = value
486                        .checked_div(rhs.0 as i128)
487                        .unwrap()
488                        .try_into()
489                        .unwrap();
490                    #[cfg(not(feature = "checked_i64"))]
491                    let result: i64 = (value / rhs.0 as i128) as i64;
492                    result
493                }
494            },
495        )
496    }
497
498    #[cfg(test)]
499    fn get_positive_test_values() -> Vec<Self> {
500        let mut result = Vec::new();
501        for i in 0..=30 {
502            result.push(FixedDecimal9(1 << i));
503        }
504        for i in 0..=30 {
505            result.push(FixedDecimal9(0x7FFF_FFFF - (1 << i)));
506        }
507        result
508    }
509}
510
511#[cfg(test)]
512mod test {
513    use super::*;
514    use crate::{
515        big_integer_tests, big_numeric_tests, integer_tests, numeric_benchmarks, numeric_tests,
516    };
517
518    integer_tests!(
519        Integer64,
520        testi_values_are_positive,
521        testi_is_zero,
522        testi_zero_is_add_neutral,
523        testi_add_is_commutative,
524        testi_opposite,
525        testi_sub_self,
526        testi_add_sub,
527        testi_sub_add,
528        testi_one_is_mul_neutral,
529        testi_mul_is_commutative,
530    );
531
532    #[cfg(not(any(feature = "checked_i64", overflow_checks)))]
533    big_integer_tests!(
534        Integer64,
535        None,
536        testi_add_is_associative,
537        testi_mul_is_associative,
538        testi_mul_is_distributive,
539    );
540    #[cfg(feature = "checked_i64")]
541    big_integer_tests!(
542        Integer64,
543        None,
544        testi_add_is_associative,
545        testi_mul_is_associative => fail(r"called `Option::unwrap()` on a `None` value"),
546        testi_mul_is_distributive,
547        testi_product => fail(r"called `Option::unwrap()` on a `None` value"),
548    );
549    #[cfg(all(not(feature = "checked_i64"), overflow_checks))]
550    big_integer_tests!(
551        Integer64,
552        None,
553        testi_add_is_associative,
554        testi_mul_is_associative => fail(r"attempt to multiply with overflow"),
555        testi_mul_is_distributive,
556        testi_product => fail(r"attempt to multiply with overflow"),
557    );
558
559    numeric_tests!(
560        Integer64,
561        FixedDecimal9,
562        test_values_are_positive,
563        test_is_exact,
564        test_ratio,
565        test_ratio_invert => fail(r"assertion `left == right` failed: R::ratio(1, a) * a != 1 for 3
566  left: FixedDecimal9(999999999)
567 right: FixedDecimal9(1000000000)"),
568        test_is_zero,
569        test_zero_is_add_neutral,
570        test_add_is_commutative,
571        test_opposite,
572        test_sub_self,
573        test_add_sub,
574        test_sub_add,
575        test_one_is_mul_neutral,
576        test_mul_is_commutative,
577        test_mul_up_is_commutative,
578        test_mul_up_integers,
579        test_mul_up_wrt_mul,
580        test_one_is_div_up_neutral,
581        test_div_up_self,
582        test_mul_div_up => fail(r"assertion `left == right` failed: div_up(a * b, b) != a for 0.000000001, 0.000000001
583  left: FixedDecimal9(0)
584 right: FixedDecimal9(1)"),
585        test_mul_by_int,
586        test_mul_div_by_int,
587        test_references,
588        test_assign,
589    );
590
591    big_numeric_tests!(
592        Integer64,
593        FixedDecimal9,
594        None,
595        test_add_is_associative,
596        test_mul_is_associative => fail(r"assertion `left == right` failed: (a * b) * c != a * (b * c) for 0.000000001, 0.536870912, 2.147483646
597  left: FixedDecimal9(0)
598 right: FixedDecimal9(1)"),
599        test_mul_is_distributive => fail(r"assertion `left == right` failed: a * (b + c) != (a * b) + (a * c) for 0.000000001, 0.134217728, 1.879048191
600  left: FixedDecimal9(2)
601 right: FixedDecimal9(1)"),
602        test_mul_by_int_is_associative,
603        test_mul_by_int_is_distributive,
604        test_div_by_int_is_associative,
605        test_div_by_int_is_distributive => fail(r"assertion `left == right` failed: (a + b) / c != (a / c) + (b / c) for 0.000000001, 0.000000001, 2
606  left: FixedDecimal9(1)
607 right: FixedDecimal9(0)"),
608        test_sum,
609        test_product,
610    );
611
612    numeric_benchmarks!(
613        Integer64,
614        FixedDecimal9,
615        bench_add,
616        bench_sub,
617        bench_mul,
618        bench_div_up,
619    );
620
621    #[test]
622    fn test_description() {
623        assert_eq!(
624            FixedDecimal9::description(),
625            "fixed-point decimal arithmetic (9 places)"
626        );
627    }
628
629    #[test]
630    fn test_display() {
631        assert_eq!(format!("{}", FixedDecimal9::zero()), "0.000000000");
632        assert_eq!(format!("{}", FixedDecimal9::one()), "1.000000000");
633        assert_eq!(format!("{}", FixedDecimal9(0)), "0.000000000");
634        assert_eq!(format!("{}", FixedDecimal9(1)), "0.000000001");
635        assert_eq!(format!("{}", FixedDecimal9(1_000_000_000)), "1.000000000");
636        assert_eq!(format!("{}", FixedDecimal9(1_234_567_890)), "1.234567890");
637        assert_eq!(format!("{}", FixedDecimal9(-1)), "-0.000000001");
638        assert_eq!(format!("{}", FixedDecimal9(-1_000_000_000)), "-1.000000000");
639    }
640
641    #[test]
642    fn test_display_test_values() {
643        #[rustfmt::skip]
644        let expected_displays = [
645            "0.000000001", "0.000000002", "0.000000004", "0.000000008",
646            "0.000000016", "0.000000032", "0.000000064", "0.000000128",
647            "0.000000256", "0.000000512", "0.000001024", "0.000002048",
648            "0.000004096", "0.000008192", "0.000016384", "0.000032768",
649            "0.000065536", "0.000131072", "0.000262144", "0.000524288",
650            "0.001048576", "0.002097152", "0.004194304", "0.008388608",
651            "0.016777216", "0.033554432", "0.067108864", "0.134217728",
652            "0.268435456", "0.536870912", "1.073741824",
653            "2.147483646", "2.147483645", "2.147483643", "2.147483639",
654            "2.147483631", "2.147483615", "2.147483583", "2.147483519",
655            "2.147483391", "2.147483135", "2.147482623", "2.147481599",
656            "2.147479551", "2.147475455", "2.147467263", "2.147450879",
657            "2.147418111", "2.147352575", "2.147221503", "2.146959359",
658            "2.146435071", "2.145386495", "2.143289343", "2.139095039",
659            "2.130706431", "2.113929215", "2.080374783", "2.013265919",
660            "1.879048191", "1.610612735", "1.073741823",
661        ];
662        let actual_displays: Vec<String> = FixedDecimal9::get_positive_test_values()
663            .iter()
664            .map(|x| format!("{x}"))
665            .collect();
666        assert_eq!(actual_displays, expected_displays);
667    }
668
669    #[test]
670    fn test_intermediate_overflow() {
671        // Even though the intermediate product overflows a i64, the result doesn't and
672        // is correct.
673        assert_eq!(
674            FixedDecimal9::from_i64(1_000) * FixedDecimal9::from_i64(1_000),
675            FixedDecimal9::from_i64(1_000_000)
676        );
677        // The intermediate result of 10^19 is just between 2^63 and 2^64. Therefore it
678        // overflows i64 as well.
679        assert_eq!(
680            FixedDecimal9::from_i64(5) * FixedDecimal9::from_i64(2),
681            FixedDecimal9::from_i64(10)
682        );
683        // The intermediate product of 10^19 overflows a i64.
684        assert_eq!(
685            FixedDecimal9::ratio(10_000_000_000, 2),
686            FixedDecimal9::from_i64(5_000_000_000)
687        );
688
689        // Same check for MulAssign.
690        let mut x = FixedDecimal9::from_i64(1_000);
691        x *= FixedDecimal9::from_i64(1_000);
692        assert_eq!(x, FixedDecimal9::from_i64(1_000_000));
693    }
694
695    #[cfg(feature = "checked_i64")]
696    #[test]
697    #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: TryFromIntError(())")]
698    fn test_mul_overflow() {
699        // The result overflows an i64, which is caught by checked_i64.
700        let _ = FixedDecimal9::from_i64(1_000_000) * FixedDecimal9::from_i64(1_000_000);
701    }
702
703    #[cfg(feature = "checked_i64")]
704    #[test]
705    #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: TryFromIntError(())")]
706    fn test_mul_assign_overflow() {
707        // The result overflows an i64, which is caught by checked_i64.
708        let mut x = FixedDecimal9::from_i64(1_000_000);
709        x *= FixedDecimal9::from_i64(1_000_000);
710    }
711
712    #[cfg(not(feature = "checked_i64"))]
713    #[test]
714    fn test_overflow() {
715        // When checked_i64 is disabled, overflow leads to incorrect values.
716        assert_eq!(
717            FixedDecimal9::from_i64(1_000_000) * FixedDecimal9::from_i64(1_000_000),
718            FixedDecimal9(3_875_820_019_684_212_736)
719        );
720
721        let mut x = FixedDecimal9::from_i64(1_000_000);
722        x *= FixedDecimal9::from_i64(1_000_000);
723        assert_eq!(x, FixedDecimal9(3_875_820_019_684_212_736));
724    }
725}