big_number/
lib.rs

1/// A simple BigNumber implementation using scientific notation
2/// for incremental/idle games, with suffixes for thousands and millions,
3/// configurable precision and trimming of trailing zeros.
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct BigNumber {
7    pub mantissa: f64,
8    pub exponent: i32,
9    /**
10     * Number of decimal places to show when representing this number with to_string(). It does not modify the underlying number's precision.
11     */
12    pub decimals: u8,
13}
14
15impl BigNumber {
16    pub fn new(mantissa: f64, exponent: i32, decimals: u8) -> Self {
17        let mut m = mantissa;
18        let mut e = exponent;
19        while m >= 10.0 && e < i32::MAX {
20            m /= 10.0;
21            e += 1;
22        }
23        while m < 1.0 && m != 0.0 && e > i32::MIN {
24            m *= 10.0;
25            e -= 1;
26        }
27        BigNumber { mantissa: m, exponent: e, decimals }
28    }
29    pub fn zero() -> Self {
30        let mut m = 0.0;
31        let mut e = 1;
32        BigNumber { mantissa: m, exponent: e, decimals: 2 }
33    }
34    pub fn one() -> Self {
35        let mut m = 1.0;
36        let mut e = 0;
37        BigNumber { mantissa: m, exponent: e, decimals: 2 }
38    }
39
40    pub fn add(self, other: BigNumber) -> BigNumber {
41        if self.exponent == other.exponent {
42            return BigNumber::new(self.mantissa + other.mantissa, self.exponent, self.decimals);
43        }
44
45        let (high, low) = if self.exponent > other.exponent {
46            (self, other)
47        } else {
48            (other, self)
49        };
50
51        let diff = high.exponent - low.exponent;
52
53        if diff > 308 { // past this number we do not really care about the lowest; we are in wild territory
54            return high;
55        }
56
57        let scaled_low = low.mantissa / 10f64.powi(diff);
58        let result_mantissa = high.mantissa + scaled_low;
59
60        BigNumber::new(result_mantissa, high.exponent, self.decimals)
61    }
62
63    pub fn sub(self, other: BigNumber) -> BigNumber {
64        if self.exponent == other.exponent {
65            BigNumber::new(self.mantissa - other.mantissa, self.exponent, self.decimals)
66        } else if self.exponent > other.exponent {
67            BigNumber::new(
68                self.mantissa - other.mantissa / 10f64.powi(self.exponent - other.exponent),
69                self.exponent,
70                self.decimals
71            )
72        } else {
73            BigNumber::new(
74                self.mantissa / 10f64.powi(other.exponent - self.exponent) - other.mantissa,
75                other.exponent,
76                self.decimals
77            )
78        }
79    }
80
81    pub fn mul(self, other: BigNumber) -> BigNumber {
82        BigNumber::new(self.mantissa * other.mantissa, self.exponent + other.exponent, self.decimals)
83    }
84
85    pub fn div(self, other: BigNumber) -> BigNumber {
86        if other.mantissa == 0.0 {
87            panic!("Division by zero");
88        }
89
90        let new_mantissa = self.mantissa / other.mantissa;
91        let new_exponent = self.exponent - other.exponent;
92
93        BigNumber::new(new_mantissa, new_exponent, self.decimals)
94    }
95
96    pub fn to_string(&self) -> String {
97        self.to_string_with_precision(self.decimals as usize)
98    }
99
100    pub fn to_string_with_precision(&self, precision: usize) -> String {
101        if self.mantissa == 0.0 {
102            return "0".to_string();
103        }
104
105        if self.exponent > 12 || self.mantissa.abs() >= 10.0 {
106            let mut mantissa_str = format!("{:.*}", precision, self.mantissa.abs());
107            if mantissa_str.contains('.') {
108                mantissa_str = mantissa_str.trim_end_matches('0').trim_end_matches('.').to_string();
109            }
110            if self.mantissa < 0.0 {
111                mantissa_str = format!("-{}", mantissa_str);
112            }
113            return format!("{}e{}", mantissa_str, self.exponent);
114        }
115
116        let val = self.mantissa * 10f64.powi(self.exponent);
117
118        let (scaled_val, suffix) = if val.abs() < 1e3 {
119            (val, "")
120        } else if val.abs() < 1e6 {
121            (val / 1e3, "K")
122        } else if val.abs() < 1e9 {
123            (val / 1e6, "M")
124        } else if val.abs() < 1e12 {
125            (val / 1e9, "B")
126        } else {
127            let mut mantissa_str = format!("{:.*}", precision, self.mantissa.abs());
128            if mantissa_str.contains('.') {
129                mantissa_str = mantissa_str.trim_end_matches('0').trim_end_matches('.').to_string();
130            }
131            if self.mantissa < 0.0 {
132                mantissa_str = format!("-{}", mantissa_str);
133            }
134            return format!("{}e{}", mantissa_str, self.exponent);
135        };
136
137        let mut s = format!("{:.*}", precision, scaled_val);
138        if s.contains('.') {
139            s = s.trim_end_matches('0').trim_end_matches('.').to_string();
140        }
141
142        s + suffix
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_addition() {
152        let a = BigNumber::new(1.0, 10, 2);
153        let b = BigNumber::new(2.0, 10, 2);
154        assert_eq!(a.add(b).to_string(), "30B");
155    }
156
157    #[test]
158    fn test_subtraction() {
159        let a = BigNumber::new(5.0, 10, 2);
160        let b = BigNumber::new(3.0, 10, 2);
161        assert_eq!(a.sub(b).to_string(), "20B");
162    }
163
164    #[test]
165    fn test_multiplication() {
166        let a = BigNumber::new(2.0, 5, 2);
167        let b = BigNumber::new(3.0, 6, 2);
168        assert_eq!(a.mul(b).to_string(), "600B");
169    }
170
171    #[test]
172    fn test_division() {
173        let a = BigNumber::new(6.0, 10, 2);
174        let b = BigNumber::new(2.0, 5, 2);
175        assert_eq!(a.div(b).to_string(), "300K");
176    }
177
178    #[test]
179    fn test_small_values() {
180        let a = BigNumber::new(5.0, 0, 2);
181        assert_eq!(a.to_string(), "5");
182        let b = BigNumber::new(1.23456, 0, 2);
183        assert_eq!(b.to_string(), "1.23");
184        assert_eq!(b.to_string_with_precision(1), "1.2");
185    }
186
187    #[test]
188    fn test_thousands_and_millions() {
189        let k = BigNumber::new(1.234, 3, 2);
190        assert_eq!(k.to_string(), "1.23K");
191        let m = BigNumber::new(5.0, 6, 2);
192        assert_eq!(m.to_string(), "5M");
193        assert_eq!(k.to_string_with_precision(2), "1.23K");
194        let t = BigNumber::new(300.0, 3, 2); // 300K
195        assert_eq!(t.to_string(), "300K");
196    }
197
198    #[test]
199    fn test_scientific_after_threshold() {
200        let c = BigNumber::new(1.0, 9, 2);
201        assert_eq!(c.to_string(), "1B");
202        assert_eq!(c.to_string_with_precision(2), "1B");
203    }
204
205    #[test]
206    fn test_zero_helper() {
207        let c = BigNumber::zero();
208        assert_eq!(c.to_string(), "0");
209    }
210
211    #[test]
212    fn test_one_helper() {
213        let c = BigNumber::one();
214        assert_eq!(c.to_string(), "1");
215    }
216    #[test]
217    fn test_overflow_creation() {
218        let max_i32 = i32::MAX;
219        let max_f64 = f64::MAX;
220        let _one = BigNumber::new(max_f64, max_i32, 2);
221    }
222    #[test]
223    fn test_underflow_creation() {
224        let max_i32 = i32::MIN;
225        let max_f64 = 0.1;
226        let _one = BigNumber::new(max_f64, max_i32, 2);
227    }
228    #[test]
229    fn test_max_representable_number() {
230        let big = BigNumber::new(f64::MAX, i32::MAX, 2);
231        let s = big.to_string_with_precision(2);
232        assert_eq!(s, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368e2147483647");
233    }
234    #[test]
235    fn test_big_representable_number() {
236        let big = BigNumber::new(f64::MAX-1.0, i32::MAX-1, 2);
237        let s = big.to_string_with_precision(2);
238        assert_eq!(s, "17976931348623157580412819756850388593900235011794141176754562789180111453639664485361928830517704263393537268510363518759043843737070229269956251768752166883397940628862983287625967246810352023792017211936260189893797509826303293149283469713429932049693599732425511693654044437030940398714664210204414967808e2147483647");
239    }
240}