use {
borsh::{BorshDeserialize, BorshSerialize},
};
const PD_EXPO: i32 = -9;
const PD_SCALE: u64 = 1_000_000_000;
const MAX_PD_V_U64: u64 = (1 << 28) - 1;
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize)]
pub struct PriceConf {
pub price: i64,
pub conf: u64,
pub expo: i32,
}
impl PriceConf {
pub fn div(&self, other: &PriceConf) -> Option<PriceConf> {
let base = self.normalize()?;
let other = other.normalize()?;
if other.price == 0 {
return None;
}
let (base_price, base_sign) = PriceConf::to_unsigned(base.price);
let (other_price, other_sign) = PriceConf::to_unsigned(other.price);
let midprice = base_price.checked_mul(PD_SCALE)?.checked_div(other_price)?;
let midprice_expo = base.expo.checked_sub(other.expo)?.checked_add(PD_EXPO)?;
let other_confidence_pct: u64 = other.conf.checked_mul(PD_SCALE)?.checked_div(other_price)?;
let conf = (base.conf.checked_mul(PD_SCALE)?.checked_div(other_price)? as u128).checked_add(
(other_confidence_pct as u128).checked_mul(midprice as u128)?.checked_div(PD_SCALE as u128)?)?;
if conf < (u64::MAX as u128) {
Some(PriceConf {
price: (midprice as i64).checked_mul(base_sign)?.checked_mul(other_sign)?,
conf: conf as u64,
expo: midprice_expo,
})
} else {
None
}
}
pub fn add(&self, other: &PriceConf) -> Option<PriceConf> {
assert_eq!(self.expo, other.expo);
let price = self.price.checked_add(other.price)?;
let conf = self.conf.checked_add(other.conf)?;
Some(PriceConf {
price,
conf,
expo: self.expo,
})
}
pub fn cmul(&self, c: i64, e: i32) -> Option<PriceConf> {
self.mul(&PriceConf { price: c, conf: 0, expo: e })
}
pub fn mul(&self, other: &PriceConf) -> Option<PriceConf> {
let base = self.normalize()?;
let other = other.normalize()?;
let (base_price, base_sign) = PriceConf::to_unsigned(base.price);
let (other_price, other_sign) = PriceConf::to_unsigned(other.price);
let midprice = base_price.checked_mul(other_price)?;
let midprice_expo = base.expo.checked_add(other.expo)?;
let conf = base.conf.checked_mul(other_price)?.checked_add(other.conf.checked_mul(base_price)?)?;
Some(PriceConf {
price: (midprice as i64).checked_mul(base_sign)?.checked_mul(other_sign)?,
conf,
expo: midprice_expo,
})
}
pub fn normalize(&self) -> Option<PriceConf> {
let (mut p, s) = PriceConf::to_unsigned(self.price);
let mut c = self.conf;
let mut e = self.expo;
while p > MAX_PD_V_U64 || c > MAX_PD_V_U64 {
p = p.checked_div(10)?;
c = c.checked_div(10)?;
e = e.checked_add(1)?;
}
Some(PriceConf {
price: (p as i64).checked_mul(s)?,
conf: c,
expo: e,
})
}
pub fn scale_to_exponent(
&self,
target_expo: i32,
) -> Option<PriceConf> {
let mut delta = target_expo.checked_sub(self.expo)?;
if delta >= 0 {
let mut p = self.price;
let mut c = self.conf;
while delta > 0 && (p != 0 || c != 0) {
p = p.checked_div(10)?;
c = c.checked_div(10)?;
delta = delta.checked_sub(1)?;
}
Some(PriceConf {
price: p,
conf: c,
expo: target_expo,
})
} else {
let mut p = self.price;
let mut c = self.conf;
while delta < 0 {
p = p.checked_mul(10)?;
c = c.checked_mul(10)?;
delta = delta.checked_add(1)?;
}
Some(PriceConf {
price: p,
conf: c,
expo: target_expo,
})
}
}
fn to_unsigned(x: i64) -> (u64, i64) {
if x == i64::MIN {
(i64::MAX as u64 + 1, -1)
} else if x < 0 {
(-x as u64, -1)
} else {
(x as u64, 1)
}
}
}
#[cfg(test)]
mod test {
use crate::price_conf::{MAX_PD_V_U64, PD_EXPO, PD_SCALE, PriceConf};
const MAX_PD_V_I64: i64 = MAX_PD_V_U64 as i64;
const MIN_PD_V_I64: i64 = -MAX_PD_V_I64;
fn pc(price: i64, conf: u64, expo: i32) -> PriceConf {
PriceConf {
price: price,
conf: conf,
expo: expo,
}
}
fn pc_scaled(price: i64, conf: u64, cur_expo: i32, expo: i32) -> PriceConf {
PriceConf {
price: price,
conf: conf,
expo: cur_expo,
}.scale_to_exponent(expo).unwrap()
}
#[test]
fn test_normalize() {
fn succeeds(
price1: PriceConf,
expected: PriceConf,
) {
assert_eq!(price1.normalize().unwrap(), expected);
}
fn fails(
price1: PriceConf,
) {
assert_eq!(price1.normalize(), None);
}
succeeds(
pc(2 * (PD_SCALE as i64), 3 * PD_SCALE, 0),
pc(2 * (PD_SCALE as i64) / 100, 3 * PD_SCALE / 100, 2)
);
succeeds(
pc(-2 * (PD_SCALE as i64), 3 * PD_SCALE, 0),
pc(-2 * (PD_SCALE as i64) / 100, 3 * PD_SCALE / 100, 2)
);
let expo = -(PD_EXPO - 2);
let scale_i64 = (PD_SCALE as i64) * 100;
let scale_u64 = scale_i64 as u64;
succeeds(pc(i64::MAX, 1, 0), pc(i64::MAX / scale_i64, 0, expo));
succeeds(pc(i64::MIN, 1, 0), pc(i64::MIN / scale_i64, 0, expo));
succeeds(pc(1, u64::MAX, 0), pc(0, u64::MAX / scale_u64, expo));
succeeds(pc(i64::MAX, 1, i32::MAX - expo), pc(i64::MAX / scale_i64, 0, i32::MAX));
fails(pc(i64::MAX, 1, i32::MAX - expo + 1));
succeeds(pc(i64::MAX, 1, i32::MIN), pc(i64::MAX / scale_i64, 0, i32::MIN + expo));
succeeds(pc(1, u64::MAX, i32::MAX - expo), pc(0, u64::MAX / scale_u64, i32::MAX));
fails(pc(1, u64::MAX, i32::MAX - expo + 1));
}
#[test]
fn test_scale_to_exponent() {
fn succeeds(
price1: PriceConf,
target: i32,
expected: PriceConf,
) {
assert_eq!(price1.scale_to_exponent(target).unwrap(), expected);
}
fn fails(
price1: PriceConf,
target: i32,
) {
assert_eq!(price1.scale_to_exponent(target), None);
}
succeeds(pc(1234, 1234, 0), 0, pc(1234, 1234, 0));
succeeds(pc(1234, 1234, 0), 1, pc(123, 123, 1));
succeeds(pc(1234, 1234, 0), 2, pc(12, 12, 2));
succeeds(pc(-1234, 1234, 0), 2, pc(-12, 12, 2));
succeeds(pc(1234, 1234, 0), 4, pc(0, 0, 4));
succeeds(pc(1234, 1234, 0), -1, pc(12340, 12340, -1));
succeeds(pc(1234, 1234, 0), -2, pc(123400, 123400, -2));
succeeds(pc(1234, 1234, 0), -8, pc(123400000000, 123400000000, -8));
fails(pc(1234, 1234, 0), -20);
fails(pc(1234, 0, 0), -20);
fails(pc(0, 1234, 0), -20);
fails(pc(1, 1, i32::MIN), i32::MAX);
}
#[test]
fn test_div() {
fn succeeds(
price1: PriceConf,
price2: PriceConf,
expected: PriceConf,
) {
assert_eq!(price1.div(&price2).unwrap(), expected);
}
fn fails(
price1: PriceConf,
price2: PriceConf,
) {
let result = price1.div(&price2);
assert_eq!(result, None);
}
succeeds(pc(1, 1, 0), pc(1, 1, 0), pc_scaled(1, 2, 0, PD_EXPO));
succeeds(pc(1, 1, -8), pc(1, 1, -8), pc_scaled(1, 2, 0, PD_EXPO));
succeeds(pc(10, 1, 0), pc(1, 1, 0), pc_scaled(10, 11, 0, PD_EXPO));
succeeds(pc(1, 1, 1), pc(1, 1, 0), pc_scaled(10, 20, 0, PD_EXPO + 1));
succeeds(pc(1, 1, 0), pc(5, 1, 0), pc_scaled(20, 24, -2, PD_EXPO));
succeeds(pc(-1, 1, 0), pc(1, 1, 0), pc_scaled(-1, 2, 0, PD_EXPO));
succeeds(pc(1, 1, 0), pc(-1, 1, 0), pc_scaled(-1, 2, 0, PD_EXPO));
succeeds(pc(-1, 1, 0), pc(-1, 1, 0), pc_scaled(1, 2, 0, PD_EXPO));
succeeds(pc(100, 10, -8), pc(2, 1, -7), pc_scaled(500_000_000, 300_000_000, -8, PD_EXPO - 1));
succeeds(pc(100, 10, -4), pc(2, 1, 0), pc_scaled(500_000, 300_000, -8, PD_EXPO + -4));
succeeds(pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc_scaled(1, 2, 0, PD_EXPO));
succeeds(pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc(1, 1, 0), pc_scaled(MAX_PD_V_I64, 2 * MAX_PD_V_U64, 0, PD_EXPO));
succeeds(pc(1, 1, 0),
pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
pc((PD_SCALE as i64) / MAX_PD_V_I64, 2 * (PD_SCALE / MAX_PD_V_U64), PD_EXPO));
succeeds(pc(MIN_PD_V_I64, MAX_PD_V_U64, 0), pc(MIN_PD_V_I64, MAX_PD_V_U64, 0), pc_scaled(1, 2, 0, PD_EXPO));
succeeds(pc(MIN_PD_V_I64, MAX_PD_V_U64, 0), pc(1, 1, 0), pc_scaled(MIN_PD_V_I64, 2 * MAX_PD_V_U64, 0, PD_EXPO));
succeeds(pc(1, 1, 0),
pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
pc((PD_SCALE as i64) / MIN_PD_V_I64, 2 * (PD_SCALE / MAX_PD_V_U64), PD_EXPO));
succeeds(pc(1, MAX_PD_V_U64, 0), pc(1, MAX_PD_V_U64, 0), pc_scaled(1, 2 * MAX_PD_V_U64, 0, PD_EXPO));
fails(pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc(1, MAX_PD_V_U64, 0));
let ten_e7: i64 = 10000000;
let uten_e7: u64 = 10000000;
succeeds(pc(520010 * ten_e7, 310 * uten_e7, -8),
pc(38591 * ten_e7, 18 * uten_e7, -8),
pc(1347490347, 1431804, -8));
let normed = pc(i64::MAX, u64::MAX, 0).normalize().unwrap();
succeeds(pc(i64::MAX, u64::MAX, 0), pc(i64::MAX, u64::MAX, 0), pc_scaled(1, 4, 0, PD_EXPO));
succeeds(pc(i64::MAX, u64::MAX, 0),
pc(1, 1, 0),
pc_scaled(normed.price, 3 * (normed.price as u64), normed.expo, normed.expo + PD_EXPO));
succeeds(pc(1, 1, 0),
pc(i64::MAX, u64::MAX, 0),
pc((PD_SCALE as i64) / normed.price, 3 * (PD_SCALE / (normed.price as u64)), PD_EXPO - normed.expo));
succeeds(pc(i64::MAX, 1, 0), pc(i64::MAX, 1, 0), pc_scaled(1, 0, 0, PD_EXPO));
succeeds(pc(i64::MAX, 1, 0),
pc(1, 1, 0),
pc_scaled(normed.price, normed.price as u64, normed.expo, normed.expo + PD_EXPO));
succeeds(pc(1, 1, 0),
pc(i64::MAX, 1, 0),
pc((PD_SCALE as i64) / normed.price, PD_SCALE / (normed.price as u64), PD_EXPO - normed.expo));
let normed = pc(i64::MIN, u64::MAX, 0).normalize().unwrap();
let normed_c = (-normed.price) as u64;
succeeds(pc(i64::MIN, u64::MAX, 0), pc(i64::MIN, u64::MAX, 0), pc_scaled(1, 4, 0, PD_EXPO));
succeeds(pc(i64::MIN, u64::MAX, 0), pc(i64::MAX, u64::MAX, 0), pc_scaled(-1, 4, 0, PD_EXPO));
succeeds(pc(i64::MIN, u64::MAX, 0),
pc(1, 1, 0),
pc_scaled(normed.price, 3 * normed_c, normed.expo, normed.expo + PD_EXPO));
succeeds(pc(1, 1, 0),
pc(i64::MIN, u64::MAX, 0),
pc((PD_SCALE as i64) / normed.price, 3 * (PD_SCALE / normed_c), PD_EXPO - normed.expo));
succeeds(pc(i64::MIN, 1, 0), pc(i64::MIN, 1, 0), pc_scaled(1, 0, 0, PD_EXPO));
succeeds(pc(i64::MIN, 1, 0),
pc(1, 1, 0),
pc_scaled(normed.price, normed_c, normed.expo, normed.expo + PD_EXPO));
succeeds(pc(1, 1, 0),
pc(i64::MIN, 1, 0),
pc((PD_SCALE as i64) / normed.price, PD_SCALE / (normed_c), PD_EXPO - normed.expo));
succeeds(pc(0, 1, 0), pc(1, 1, 0), pc_scaled(0, 1, 0, PD_EXPO));
succeeds(pc(0, 1, 0), pc(100, 1, 0), pc_scaled(0, 1, -2, PD_EXPO));
fails(pc(1, 1, 0), pc(0, 1, 0));
fails(pc(1, 1, 0), pc(1, u64::MAX, 0));
succeeds(
pc(1, u64::MAX, 0),
pc(1, 1, 0),
pc_scaled(0, normed.conf, normed.expo, normed.expo + PD_EXPO)
);
succeeds(pc(1, 1, i32::MAX), pc(1, 1, 0), pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MAX + PD_EXPO));
fails(pc(1, 1, i32::MAX), pc(1, 1, -1));
succeeds(pc(1, 1, i32::MIN - PD_EXPO), pc(1, 1, 0), pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MIN));
succeeds(pc(1, 1, i32::MIN), pc(1, 1, PD_EXPO), pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MIN));
fails(pc(1, 1, i32::MIN - PD_EXPO), pc(1, 1, 1));
}
#[test]
fn test_mul() {
fn succeeds(
price1: PriceConf,
price2: PriceConf,
expected: PriceConf,
) {
assert_eq!(price1.mul(&price2).unwrap(), expected);
}
fn fails(
price1: PriceConf,
price2: PriceConf,
) {
let result = price1.mul(&price2);
assert_eq!(result, None);
}
succeeds(pc(1, 1, 0), pc(1, 1, 0), pc(1, 2, 0));
succeeds(pc(1, 1, -8), pc(1, 1, -8), pc(1, 2, -16));
succeeds(pc(10, 1, 0), pc(1, 1, 0), pc(10, 11, 0));
succeeds(pc(1, 1, 1), pc(1, 1, 0), pc(1, 2, 1));
succeeds(pc(1, 1, 0), pc(5, 1, 0), pc(5, 6, 0));
succeeds(pc(100, 10, -8), pc(2, 1, -7), pc(200, 120, -15));
succeeds(pc(100, 10, -4), pc(2, 1, 0), pc(200, 120, -4));
succeeds(pc(0, 10, -4), pc(2, 1, 0), pc(0, 20, -4));
succeeds(pc(2, 1, 0), pc(0, 10, -4), pc(0, 20, -4));
succeeds(
pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
pc(MAX_PD_V_I64 * MAX_PD_V_I64, 2 * MAX_PD_V_U64 * MAX_PD_V_U64, 0)
);
succeeds(pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc(1, 1, 0), pc(MAX_PD_V_I64, 2 * MAX_PD_V_U64, 0));
succeeds(
pc(1, MAX_PD_V_U64, 0),
pc(3, 1, 0),
pc(3, 1 + 3 * MAX_PD_V_U64, 0)
);
succeeds(pc(1, MAX_PD_V_U64, 0), pc(1, MAX_PD_V_U64, 0), pc(1, 2 * MAX_PD_V_U64, 0));
succeeds(
pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
pc(1, MAX_PD_V_U64, 0),
pc(MAX_PD_V_I64, MAX_PD_V_U64 + MAX_PD_V_U64 * MAX_PD_V_U64, 0)
);
succeeds(
pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
pc(MIN_PD_V_I64 * MIN_PD_V_I64, 2 * MAX_PD_V_U64 * MAX_PD_V_U64, 0)
);
succeeds(
pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
pc(MIN_PD_V_I64 * MAX_PD_V_I64, 2 * MAX_PD_V_U64 * MAX_PD_V_U64, 0)
);
succeeds(pc(MIN_PD_V_I64, MAX_PD_V_U64, 0), pc(1, 1, 0), pc(MIN_PD_V_I64, 2 * MAX_PD_V_U64, 0));
succeeds(
pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
pc(1, MAX_PD_V_U64, 0),
pc(MIN_PD_V_I64, MAX_PD_V_U64 + MAX_PD_V_U64 * MAX_PD_V_U64, 0)
);
let ten_e7: i64 = 10000000;
let uten_e7: u64 = 10000000;
succeeds(
pc(3 * (PD_SCALE as i64), 3 * PD_SCALE, PD_EXPO),
pc(2 * (PD_SCALE as i64), 4 * PD_SCALE, PD_EXPO),
pc(6 * ten_e7 * ten_e7, 18 * uten_e7 * uten_e7, -14)
);
let normed = pc(i64::MAX, u64::MAX, 0).normalize().unwrap();
succeeds(
pc(i64::MAX, u64::MAX, 0),
pc(i64::MAX, u64::MAX, 0),
pc(normed.price * normed.price, 4 * ((normed.price * normed.price) as u64), normed.expo * 2)
);
succeeds(pc(i64::MAX, u64::MAX, 0),
pc(1, 1, 0),
pc(normed.price, 3 * (normed.price as u64), normed.expo));
succeeds(
pc(i64::MAX, 1, 0),
pc(i64::MAX, 1, 0),
pc(normed.price * normed.price, 0, normed.expo * 2)
);
succeeds(pc(i64::MAX, 1, 0),
pc(1, 1, 0),
pc(normed.price, normed.price as u64, normed.expo));
let normed = pc(i64::MIN, u64::MAX, 0).normalize().unwrap();
let normed_c = (-normed.price) as u64;
succeeds(
pc(i64::MIN, u64::MAX, 0),
pc(i64::MIN, u64::MAX, 0),
pc(normed.price * normed.price, 4 * (normed_c * normed_c), normed.expo * 2)
);
succeeds(pc(i64::MIN, u64::MAX, 0),
pc(1, 1, 0),
pc(normed.price, 3 * normed_c, normed.expo));
succeeds(
pc(i64::MIN, 1, 0),
pc(i64::MIN, 1, 0),
pc(normed.price * normed.price, 0, normed.expo * 2)
);
succeeds(pc(i64::MIN, 1, 0),
pc(1, 1, 0),
pc(normed.price, normed_c, normed.expo));
succeeds(pc(1, 1, i32::MAX), pc(1, 1, 0), pc(1, 2, i32::MAX));
succeeds(pc(1, 1, i32::MAX), pc(1, 1, -1), pc(1, 2, i32::MAX - 1));
fails(pc(1, 1, i32::MAX), pc(1, 1, 1));
succeeds(pc(1, 1, i32::MIN), pc(1, 1, 0), pc(1, 2, i32::MIN));
succeeds(pc(1, 1, i32::MIN), pc(1, 1, 1), pc(1, 2, i32::MIN + 1));
fails(pc(1, 1, i32::MIN), pc(1, 1, -1));
}
}