use crate::constants::SCALE_I;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DoubleWord {
hi: i128,
lo: i128,
}
impl DoubleWord {
#[inline]
pub const fn from_hi(hi: i128) -> Self {
Self { hi, lo: 0 }
}
#[inline]
pub(crate) const fn new_raw(hi: i128, lo: i128) -> Self {
Self { hi, lo }
}
#[inline]
pub fn to_i128(self) -> i128 {
self.to_i128_at_scale(SCALE_I)
}
#[inline]
pub(crate) fn to_i128_at_scale(self, scale: i128) -> i128 {
let half = scale / 2;
let correction = if self.lo >= 0 {
(self.lo + half) / scale
} else {
(self.lo - half) / scale
};
self.hi + correction
}
#[allow(dead_code)]
#[inline]
pub(crate) fn add(self, other: Self) -> Result<Self, crate::error::SolMathError> {
let lo_sum = self.lo + other.lo;
let carry = if lo_sum >= SCALE_I {
1
} else if lo_sum <= -SCALE_I {
-1
} else {
0
};
let hi = self.hi.checked_add(other.hi)
.and_then(|h| h.checked_add(carry))
.ok_or(crate::error::SolMathError::Overflow)?;
Ok(Self {
hi,
lo: lo_sum - carry * SCALE_I,
})
}
#[allow(dead_code)]
#[inline]
pub(crate) const fn hi(self) -> i128 {
self.hi
}
#[allow(dead_code)]
#[inline]
pub(crate) const fn lo(self) -> i128 {
self.lo
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::SCALE_I;
#[test]
fn test_dw_from_hi() {
let dw = DoubleWord::from_hi(42 * SCALE_I);
assert_eq!(dw.hi, 42 * SCALE_I);
assert_eq!(dw.lo, 0);
}
#[test]
fn test_dw_to_i128_no_correction() {
let dw = DoubleWord { hi: 5 * SCALE_I, lo: 0 };
assert_eq!(dw.to_i128(), 5 * SCALE_I);
}
#[test]
fn test_dw_to_i128_positive_correction() {
let dw = DoubleWord { hi: 5 * SCALE_I, lo: SCALE_I / 2 };
assert_eq!(dw.to_i128(), 5 * SCALE_I + 1);
}
#[test]
fn test_dw_to_i128_negative_correction() {
let dw = DoubleWord { hi: 5 * SCALE_I, lo: -SCALE_I / 2 };
assert_eq!(dw.to_i128(), 5 * SCALE_I - 1);
}
#[test]
fn test_dw_to_i128_small_lo_no_correction() {
let dw = DoubleWord { hi: 5 * SCALE_I, lo: SCALE_I / 2 - 1 };
assert_eq!(dw.to_i128(), 5 * SCALE_I);
}
#[test]
fn test_dw_add_simple() {
let a = DoubleWord { hi: 3 * SCALE_I, lo: 100 };
let b = DoubleWord { hi: 4 * SCALE_I, lo: 200 };
let c = a.add(b).unwrap();
assert_eq!(c.hi, 7 * SCALE_I);
assert_eq!(c.lo, 300);
}
#[test]
fn test_dw_add_lo_carry() {
let a = DoubleWord { hi: 3 * SCALE_I, lo: SCALE_I - 100 };
let b = DoubleWord { hi: 4 * SCALE_I, lo: 200 };
let c = a.add(b).unwrap();
assert_eq!(c.hi, 7 * SCALE_I + 1);
assert_eq!(c.lo, 100);
}
#[test]
fn test_dw_add_lo_negative_carry() {
let a = DoubleWord { hi: 3 * SCALE_I, lo: -(SCALE_I - 100) };
let b = DoubleWord { hi: 4 * SCALE_I, lo: -200 };
let c = a.add(b).unwrap();
assert_eq!(c.hi, 7 * SCALE_I - 1);
assert_eq!(c.lo, -100);
}
#[test]
fn test_dw_invariant_lo_bounded() {
let extremes = [
DoubleWord { hi: 0, lo: SCALE_I - 1 },
DoubleWord { hi: 0, lo: -(SCALE_I - 1) },
DoubleWord { hi: 1_000 * SCALE_I, lo: SCALE_I - 1 },
DoubleWord { hi: -1_000 * SCALE_I, lo: -(SCALE_I - 1) },
];
for &a in &extremes {
for &b in &extremes {
let c = a.add(b).unwrap();
assert!(c.lo.abs() < SCALE_I,
"lo invariant violated: a={:?}, b={:?}, result={:?}", a, b, c);
}
}
}
#[test]
fn test_dw_to_i128_roundtrip() {
for x in [0i128, 1, -1, SCALE_I, -SCALE_I, i128::MAX / 2, i128::MIN / 2] {
assert_eq!(DoubleWord::from_hi(x).to_i128(), x);
}
}
#[test]
fn test_dw_at_scale_matches_to_i128() {
let cases = [
DoubleWord { hi: 5 * SCALE_I, lo: 0 },
DoubleWord { hi: 5 * SCALE_I, lo: SCALE_I / 2 },
DoubleWord { hi: 5 * SCALE_I, lo: -SCALE_I / 2 },
DoubleWord { hi: 5 * SCALE_I, lo: SCALE_I / 2 - 1 },
DoubleWord { hi: 0, lo: 0 },
DoubleWord { hi: -3 * SCALE_I, lo: 100 },
DoubleWord { hi: -3 * SCALE_I, lo: -100 },
];
for dw in cases {
assert_eq!(dw.to_i128(), dw.to_i128_at_scale(SCALE_I),
"Mismatch for {:?}", dw);
}
}
#[test]
fn test_dw_at_scale_hp() {
use crate::constants::SCALE_HP;
let dw = DoubleWord { hi: 5 * SCALE_HP, lo: SCALE_HP / 2 };
assert_eq!(dw.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP + 1);
let dw2 = DoubleWord { hi: 5 * SCALE_HP, lo: SCALE_HP / 2 - 1 };
assert_eq!(dw2.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP);
}
#[test]
fn test_dw_at_scale_hp_negative() {
use crate::constants::SCALE_HP;
let dw = DoubleWord { hi: 5 * SCALE_HP, lo: -SCALE_HP / 2 };
assert_eq!(dw.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP - 1);
let dw2 = DoubleWord { hi: 5 * SCALE_HP, lo: -(SCALE_HP / 2 - 1) };
assert_eq!(dw2.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP);
}
#[test]
fn test_dw_at_scale_hp_roundtrip() {
use crate::constants::SCALE_HP;
for x in [0i128, 1, -1, SCALE_HP, -SCALE_HP] {
assert_eq!(DoubleWord::from_hi(x).to_i128_at_scale(SCALE_HP), x);
}
}
}