brine_fp/
signed.rs

1use super::unsigned::UnsignedNumeric;
2use core::ops::{Add, Sub, Mul, Div};
3
4#[cfg(not(feature = "std"))]
5use alloc::{format, string::String};
6
7// Based on the following implementations:
8// https://github.com/solana-labs/solana-program-library/blob/v2.0/libraries/math/src/precise_number.rs
9// https://github.com/StrataFoundation/strata/blob/master/programs/spl-token-bonding/src/signed_precise_number.rs
10
11/// A `SignedNumeric` represents a signed 192-bit fixed-point number with 18 decimal places of precision.
12///
13/// This struct extends [`UnsignedNumeric`] by adding a `bool` flag to indicate whether the value is negative,
14/// enabling full support for signed decimal arithmetic.
15///
16/// ### Internal Representation
17/// - The magnitude is stored as a [`UnsignedNumeric`], which wraps a [`InnerUint`] representing a 192-bit unsigned integer scaled by 10¹⁸.
18/// - The `is_negative` flag determines the sign of the number.
19///
20/// ### Interpretation
21/// The real-world value of a `SignedNumeric` is:
22/// ```text
23/// value = (is_negative ? -1 : 1) × (magnitude / 10^18)
24/// ```
25///
26/// ### Examples:
27/// - `value = UnsignedNumeric::from_u192([1_000_000_000_000_000_000, 0, 0]), is_negative = false` → 1.0
28/// - `value = UnsignedNumeric::from_u192([5_000_000_000_000_000_000, 0, 0]), is_negative = true`  → -5.0
29///
30/// This format is useful for financial and scientific applications where both precision and sign are critical,
31/// and where floating-point inaccuracies are unacceptable.
32#[derive(Clone, Debug, PartialEq)]
33pub struct SignedNumeric {
34    pub value: UnsignedNumeric,
35    pub is_negative: bool,
36}
37
38impl SignedNumeric {
39    pub fn new(value: i128) -> Self {
40        let abs_value = value.unsigned_abs();
41        let is_negative = value < 0;
42        Self {
43            value: UnsignedNumeric::new(abs_value),
44            is_negative,
45        }
46    }
47    pub fn negate(&self) -> SignedNumeric {
48        SignedNumeric {
49            value: self.value.clone(),
50            is_negative: !self.is_negative,
51        }
52    }
53
54    pub fn checked_mul(&self, rhs: &Self) -> Option<SignedNumeric> {
55        Some(SignedNumeric {
56            value: self.value.checked_mul(&rhs.value)?,
57            is_negative: (self.is_negative || rhs.is_negative)
58                && !(self.is_negative && rhs.is_negative),
59        })
60    }
61
62    pub fn checked_div(&self, rhs: &Self) -> Option<SignedNumeric> {
63        Some(SignedNumeric {
64            value: self.value.checked_div(&rhs.value)?,
65            is_negative: (self.is_negative || rhs.is_negative)
66                && !(self.is_negative && rhs.is_negative),
67        })
68    }
69
70    pub fn checked_add(&self, rhs: &Self) -> Option<SignedNumeric> {
71        let lhs_negative = self.is_negative;
72        let rhs_negative = rhs.is_negative;
73
74        if rhs_negative && lhs_negative {
75            Some(Self {
76                value: self.value.checked_add(&rhs.value)?,
77                is_negative: true,
78            })
79        } else if rhs_negative {
80            if rhs.value.greater_than(&self.value) {
81                Some(Self {
82                    value: rhs.value.checked_sub(&self.value)?,
83                    is_negative: true,
84                })
85            } else {
86                Some(Self {
87                    value: self.value.checked_sub(&rhs.value)?,
88                    is_negative: false,
89                })
90            }
91        } else if lhs_negative {
92            if self.value.greater_than(&rhs.value) {
93                Some(Self {
94                    value: self.value.checked_sub(&rhs.value)?,
95                    is_negative: true,
96                })
97            } else {
98                Some(Self {
99                    value: rhs.value.checked_sub(&self.value)?,
100                    is_negative: false,
101                })
102            }
103        } else {
104            Some(Self {
105                value: self.value.checked_add(&rhs.value)?,
106                is_negative: false,
107            })
108        }
109    }
110
111    pub fn checked_sub(&self, rhs: &Self) -> Option<SignedNumeric> {
112        self.checked_add(&rhs.clone().negate())
113    }
114
115    pub fn floor(&self) -> Option<SignedNumeric> {
116        Some(Self {
117            value: self.value.floor()?,
118            is_negative: self.is_negative,
119        })
120    }
121
122    pub fn to_string(&self) -> String {
123        let sign = if self.is_negative { "-" } else { "" };
124        format!("{}{}", sign, self.value.to_string())
125    }
126}
127
128// Standard arithmetic trait implementations
129impl Add for SignedNumeric {
130    type Output = Self;
131
132    fn add(self, rhs: Self) -> Self::Output {
133        self.checked_add(&rhs).unwrap()
134    }
135}
136
137impl Add<&SignedNumeric> for SignedNumeric {
138    type Output = Self;
139
140    fn add(self, rhs: &Self) -> Self::Output {
141        self.checked_add(rhs).unwrap()
142    }
143}
144
145impl Add<SignedNumeric> for &SignedNumeric {
146    type Output = SignedNumeric;
147
148    fn add(self, rhs: SignedNumeric) -> Self::Output {
149        self.checked_add(&rhs).unwrap()
150    }
151}
152
153impl Add<&SignedNumeric> for &SignedNumeric {
154    type Output = SignedNumeric;
155
156    fn add(self, rhs: &SignedNumeric) -> Self::Output {
157        self.checked_add(rhs).unwrap()
158    }
159}
160
161impl Sub for SignedNumeric {
162    type Output = Self;
163
164    fn sub(self, rhs: Self) -> Self::Output {
165        self.checked_sub(&rhs).unwrap()
166    }
167}
168
169impl Sub<&SignedNumeric> for SignedNumeric {
170    type Output = Self;
171
172    fn sub(self, rhs: &Self) -> Self::Output {
173        self.checked_sub(rhs).unwrap()
174    }
175}
176
177impl Sub<SignedNumeric> for &SignedNumeric {
178    type Output = SignedNumeric;
179
180    fn sub(self, rhs: SignedNumeric) -> Self::Output {
181        self.checked_sub(&rhs).unwrap()
182    }
183}
184
185impl Sub<&SignedNumeric> for &SignedNumeric {
186    type Output = SignedNumeric;
187
188    fn sub(self, rhs: &SignedNumeric) -> Self::Output {
189        self.checked_sub(rhs).unwrap()
190    }
191}
192
193impl Mul for SignedNumeric {
194    type Output = Self;
195
196    fn mul(self, rhs: Self) -> Self::Output {
197        self.checked_mul(&rhs).unwrap()
198    }
199}
200
201impl Mul<&SignedNumeric> for SignedNumeric {
202    type Output = Self;
203
204    fn mul(self, rhs: &Self) -> Self::Output {
205        self.checked_mul(rhs).unwrap()
206    }
207}
208
209impl Mul<SignedNumeric> for &SignedNumeric {
210    type Output = SignedNumeric;
211
212    fn mul(self, rhs: SignedNumeric) -> Self::Output {
213        self.checked_mul(&rhs).unwrap()
214    }
215}
216
217impl Mul<&SignedNumeric> for &SignedNumeric {
218    type Output = SignedNumeric;
219
220    fn mul(self, rhs: &SignedNumeric) -> Self::Output {
221        self.checked_mul(rhs).unwrap()
222    }
223}
224
225impl Div for SignedNumeric {
226    type Output = Self;
227
228    fn div(self, rhs: Self) -> Self::Output {
229        self.checked_div(&rhs).unwrap()
230    }
231}
232
233impl Div<&SignedNumeric> for SignedNumeric {
234    type Output = Self;
235
236    fn div(self, rhs: &Self) -> Self::Output {
237        self.checked_div(rhs).unwrap()
238    }
239}
240
241impl Div<SignedNumeric> for &SignedNumeric {
242    type Output = SignedNumeric;
243
244    fn div(self, rhs: SignedNumeric) -> Self::Output {
245        self.checked_div(&rhs).unwrap()
246    }
247}
248
249impl Div<&SignedNumeric> for &SignedNumeric {
250    type Output = SignedNumeric;
251
252    fn div(self, rhs: &SignedNumeric) -> Self::Output {
253        self.checked_div(rhs).unwrap()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::consts::*;
261    use crate::InnerUint;
262
263    fn signed(val: u128, is_negative: bool) -> SignedNumeric {
264        SignedNumeric {
265            value: UnsignedNumeric::new(val),
266            is_negative,
267        }
268    }
269
270    #[test]
271    fn test_negate() {
272        let a = signed(5, false);
273        let b = a.negate();
274        assert_eq!(b.value, a.value);
275        assert_eq!(b.is_negative, true);
276        assert_eq!(b.negate(), a);
277    }
278
279    #[test]
280    fn test_add_same_sign_positive() {
281        let a = signed(3, false);
282        let b = signed(2, false);
283        let sum = a.checked_add(&b).unwrap();
284        assert_eq!(sum.value.to_imprecise().unwrap(), 5);
285        assert!(!sum.is_negative);
286    }
287
288    #[test]
289    fn test_add_same_sign_negative() {
290        let a = signed(3, true);
291        let b = signed(2, true);
292        let sum = a.checked_add(&b).unwrap();
293        assert_eq!(sum.value.to_imprecise().unwrap(), 5);
294        assert!(sum.is_negative);
295    }
296
297    #[test]
298    fn test_add_opposite_sign_larger_positive() {
299        let a = signed(5, false);
300        let b = signed(3, true);
301        let sum = a.checked_add(&b).unwrap();
302        assert_eq!(sum.value.to_imprecise().unwrap(), 2);
303        assert!(!sum.is_negative);
304    }
305
306    #[test]
307    fn test_add_opposite_sign_larger_negative() {
308        let a = signed(3, false);
309        let b = signed(5, true);
310        let sum = a.checked_add(&b).unwrap();
311        assert_eq!(sum.value.to_imprecise().unwrap(), 2);
312        assert!(sum.is_negative);
313    }
314
315    #[test]
316    fn test_add_opposite_sign_equal() {
317        let a = signed(4, false);
318        let b = signed(4, true);
319        let sum = a.checked_add(&b).unwrap();
320        assert_eq!(sum.value.to_imprecise().unwrap(), 0);
321        assert!(!sum.is_negative);
322    }
323
324    #[test]
325    fn test_sub_positive() {
326        let a = signed(10, false);
327        let b = signed(3, false);
328        let diff = a.checked_sub(&b).unwrap();
329        assert_eq!(diff.value.to_imprecise().unwrap(), 7);
330        assert!(!diff.is_negative);
331    }
332
333    #[test]
334    fn test_sub_negative() {
335        let a = signed(3, false);
336        let b = signed(10, false);
337        let diff = a.checked_sub(&b).unwrap();
338        assert_eq!(diff.value.to_imprecise().unwrap(), 7);
339        assert!(diff.is_negative);
340    }
341
342    #[test]
343    fn test_sub_negative_minued() {
344        let a = signed(3, true);
345        let b = signed(10, false);
346        let diff = a.checked_sub(&b).unwrap();
347        assert_eq!(diff.value.to_imprecise().unwrap(), 13);
348        assert!(diff.is_negative);
349    }
350
351    #[test]
352    fn test_mul_signs() {
353        let a = signed(3, false);
354        let b = signed(2, false);
355        let result = a.checked_mul(&b).unwrap();
356        assert_eq!(result.value.to_imprecise().unwrap(), 6);
357        assert!(!result.is_negative);
358
359        let result = a.checked_mul(&b.negate()).unwrap();
360        assert_eq!(result.value.to_imprecise().unwrap(), 6);
361        assert!(result.is_negative);
362
363        let result = a.negate().checked_mul(&b.negate()).unwrap();
364        assert_eq!(result.value.to_imprecise().unwrap(), 6);
365        assert!(!result.is_negative);
366    }
367
368    #[test]
369    fn test_div_signs() {
370        let a = signed(6, false);
371        let b = signed(2, false);
372        let result = a.checked_div(&b).unwrap();
373        assert_eq!(result.value.to_imprecise().unwrap(), 3);
374        assert!(!result.is_negative);
375
376        let result = a.checked_div(&b.negate()).unwrap();
377        assert_eq!(result.value.to_imprecise().unwrap(), 3);
378        assert!(result.is_negative);
379
380        let result = a.negate().checked_div(&b.negate()).unwrap();
381        assert_eq!(result.value.to_imprecise().unwrap(), 3);
382        assert!(!result.is_negative);
383    }
384
385    #[test]
386    fn test_floor_behavior() {
387        let base = signed(2, false);
388        let mut with_decimals = base.clone();
389        with_decimals.value.value = with_decimals
390            .value
391            .value
392            .checked_add(InnerUint::from(ONE / 3))
393            .unwrap();
394        let floored = with_decimals.floor().unwrap();
395        assert_eq!(floored.value, base.value);
396        assert_eq!(floored.is_negative, false);
397
398        let base_neg = base.negate();
399        let mut neg_with_decimals = base_neg.clone();
400        neg_with_decimals.value.value = neg_with_decimals
401            .value
402            .value
403            .checked_add(InnerUint::from(ONE / 2))
404            .unwrap();
405        let floored = neg_with_decimals.floor().unwrap();
406        assert_eq!(floored.value, base.value);
407        assert_eq!(floored.is_negative, true);
408    }
409
410    #[test]
411    fn test_to_string_exact() {
412        let n = signed(3, false);
413        assert_eq!(n.to_string(), "3.000000000000000000");
414        
415        let n_neg = signed(3, true);
416        assert_eq!(n_neg.to_string(), "-3.000000000000000000");
417    }
418
419    #[test]
420    fn test_to_string_fractional() {
421        let mut n = signed(3, false);
422        n.value.value += InnerUint::from(250_000_000_000_000_000u128); // +0.25
423        assert_eq!(n.to_string(), "3.250000000000000000");
424        
425        let mut n_neg = signed(3, true);
426        n_neg.value.value += InnerUint::from(250_000_000_000_000_000u128); // +0.25
427        assert_eq!(n_neg.to_string(), "-3.250000000000000000");
428    }
429
430    #[test]
431    fn test_arithmetic_operators() {
432        let a = signed(5, false);
433        let b = signed(3, false);
434        let c = signed(2, true);
435        
436        // Test addition
437        let sum1 = a.clone() + b.clone();
438        assert_eq!(sum1.value.to_imprecise().unwrap(), 8);
439        assert!(!sum1.is_negative);
440        
441        let sum2 = a.clone() + c.clone();
442        assert_eq!(sum2.value.to_imprecise().unwrap(), 3);
443        assert!(!sum2.is_negative);
444        
445        // Test subtraction
446        let diff1 = a.clone() - b.clone();
447        assert_eq!(diff1.value.to_imprecise().unwrap(), 2);
448        assert!(!diff1.is_negative);
449        
450        let diff2 = b.clone() - a.clone();
451        assert_eq!(diff2.value.to_imprecise().unwrap(), 2);
452        assert!(diff2.is_negative);
453        
454        // Test multiplication
455        let product1 = a.clone() * b.clone();
456        assert_eq!(product1.value.to_imprecise().unwrap(), 15);
457        assert!(!product1.is_negative);
458        
459        let product2 = a.clone() * c.clone();
460        assert_eq!(product2.value.to_imprecise().unwrap(), 10);
461        assert!(product2.is_negative);
462        
463        // Test division
464        let quotient1 = a.clone() / b.clone();
465        assert!(quotient1.value.almost_eq(&UnsignedNumeric::from_scaled_u128(1_666_666_666_666_666_666), InnerUint::from(1_000_000)));
466        assert!(!quotient1.is_negative);
467        
468        let quotient2 = a.clone() / c.clone();
469        assert_eq!(quotient2.value.to_imprecise().unwrap(), 3);
470        assert!(quotient2.is_negative);
471    }
472
473    #[test]
474    fn test_arithmetic_operators_with_references() {
475        let a = signed(10, false);
476        let b = signed(4, false);
477        let c = signed(3, true);
478        
479        // Test with references
480        let sum = &a + &b;
481        assert_eq!(sum.value.to_imprecise().unwrap(), 14);
482        assert!(!sum.is_negative);
483        
484        let diff = &a - &b;
485        assert_eq!(diff.value.to_imprecise().unwrap(), 6);
486        assert!(!diff.is_negative);
487        
488        let product = &a * &c;
489        assert_eq!(product.value.to_imprecise().unwrap(), 30);
490        assert!(product.is_negative);
491        
492        let quotient = &a / &b;
493        assert_eq!(quotient.value.to_imprecise().unwrap(), 3);
494        assert!(!quotient.is_negative);
495    }
496
497    #[test]
498    fn test_arithmetic_operators_mixed_references() {
499        let a = signed(6, false);
500        let b = signed(2, true);
501        
502        // Test mixed reference patterns
503        let sum1 = a.clone() + &b;
504        let sum2 = &a + b.clone();
505        assert_eq!(sum1.value.to_imprecise().unwrap(), 4);
506        assert!(!sum1.is_negative);
507        assert_eq!(sum2.value.to_imprecise().unwrap(), 4);
508        assert!(!sum2.is_negative);
509        
510        let product1 = a.clone() * &b;
511        let product2 = &a * b.clone();
512        assert_eq!(product1.value.to_imprecise().unwrap(), 12);
513        assert!(product1.is_negative);
514        assert_eq!(product2.value.to_imprecise().unwrap(), 12);
515        assert!(product2.is_negative);
516    }
517
518    #[test]
519    fn test_negative_arithmetic() {
520        let a = signed(5, true);  // -5
521        let b = signed(3, true);  // -3
522        
523        // Test negative addition
524        let sum = a.clone() + b.clone();
525        assert_eq!(sum.value.to_imprecise().unwrap(), 8);
526        assert!(sum.is_negative);
527        
528        // Test negative multiplication
529        let product = a.clone() * b.clone();
530        assert_eq!(product.value.to_imprecise().unwrap(), 15);
531        assert!(!product.is_negative); // negative * negative = positive
532        
533        // Test negative division
534        let quotient = a.clone() / b.clone();
535        assert!(quotient.value.almost_eq(&UnsignedNumeric::from_scaled_u128(1_666_666_666_666_666_666), InnerUint::from(1_000_000)));
536        assert!(!quotient.is_negative); // negative / negative = positive
537    }
538}