use std::cmp::Ordering;
use std::str::FromStr;
use num_traits::{FromPrimitive, ToPrimitive};
use primitive_types::U256;
use rust_decimal::Decimal;
use super::fixed256::Fixed256;
#[derive(Clone, Copy, Default)]
pub struct Fixed128<const PRECISION: u8> {
raw: u128,
scale_override: Option<u8>,
}
impl<const PRECISION: u8> PartialEq for Fixed128<PRECISION> {
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
impl<const PRECISION: u8> Eq for Fixed128<PRECISION> {}
impl<const PRECISION: u8> PartialOrd for Fixed128<PRECISION> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<const PRECISION: u8> Ord for Fixed128<PRECISION> {
fn cmp(&self, other: &Self) -> Ordering {
self.raw.cmp(&other.raw)
}
}
impl<const PRECISION: u8> Fixed128<PRECISION> {
const fn new_const(raw: u128, scale_override: Option<u8>) -> Self {
Self { raw, scale_override }
}
fn new_with_scale(raw: u128, scale_override: Option<u8>) -> Self {
Self {
raw,
scale_override: scale_override.map(|scale| scale.min(PRECISION)),
}
}
pub const ONE: Self = Self::new_const(10u128.pow(PRECISION as u32), Some(0));
pub const MAX: Self = Self::new_const(u128::MAX, None);
pub const ZERO: Self = Self::new_const(0, Some(0));
pub const MAX_INT: u128 = u128::MAX / Self::ONE.raw;
pub const MAX_SCALE: u8 = PRECISION;
pub const fn from_raw(val: u128) -> Self {
Self::new_const(val, None)
}
pub fn from_raw_with_scale(val: u128, scale_override: Option<u8>) -> Self {
Self::new_with_scale(val, scale_override)
}
pub const fn into_raw(self) -> u128 {
self.raw
}
const fn intrinsic_scale(raw: u128) -> u8 {
let mut frac = raw % Self::ONE.raw;
let mut c = 0;
if frac == 0 {
return 0;
}
while frac.is_multiple_of(10) {
frac /= 10;
c += 1;
}
PRECISION - c
}
const fn minimal_scale(self) -> u8 {
Self::intrinsic_scale(self.raw)
}
pub const fn scale(self) -> u8 {
match self.scale_override {
Some(scale) => scale,
None => self.minimal_scale(),
}
}
pub const fn from_int(int: u128) -> Self {
Self::new_const(int * Self::ONE.raw, Some(0))
}
pub fn from_int_checked(int: u128) -> Option<Self> {
int.checked_mul(Self::ONE.raw).map(|raw| Self::new_with_scale(raw, Some(0)))
}
pub fn from_float(f: f64) -> Self {
let int = f as u128;
let frac = (f.fract() * (Self::ONE.raw as f64)) as u128;
Self::new_with_scale(int * Self::ONE.raw + frac, None)
}
pub fn from_float_checked(f: f64) -> Option<Self> {
let int = f as u128;
let frac = (f.fract() * Self::ONE.raw as f64) as u128;
int.checked_mul(Self::ONE.raw).and_then(|x| x.checked_add(frac)).map(|raw| Self::new_with_scale(raw, None))
}
pub fn from_decimal(f: rust_decimal::Decimal) -> Self {
let int = f.to_u128().unwrap();
let frac = (f.fract() * Decimal::from_u128(Self::ONE.raw).unwrap()).to_u128().unwrap();
let scale = (f.scale() as u8).min(PRECISION);
Self::new_with_scale(int * Self::ONE.raw + frac, Some(scale))
}
pub fn from_decimal_checked(f: rust_decimal::Decimal) -> Option<Self> {
if f.scale() > PRECISION as u32 {
return None;
}
let int = f.to_u128()?;
let frac = (f.fract() * Decimal::from_u128(Self::ONE.raw)?).to_u128()?;
let scale = Some((f.scale() as u8).min(PRECISION));
int.checked_mul(Self::ONE.raw).and_then(|x| x.checked_add(frac)).map(|raw| Self::new_with_scale(raw, scale))
}
pub const fn from_fraction(x: u128, y: u128) -> Self {
if x == 0 || y == 0 {
return Self::ZERO;
}
if x >= Self::MAX_INT {
return Self::new_const(Self::ONE.raw / y * x, None);
}
Self::new_const(x * Self::ONE.raw / y, None)
}
pub const fn int_part(self) -> u128 {
self.raw / Self::ONE.raw
}
pub const fn frac_part(self) -> u128 {
self.raw % Self::ONE.raw
}
pub fn int_part_decimal(self) -> Decimal {
Decimal::new(self.int_part() as i64, 0)
}
pub fn frac_part_decimal(self) -> Decimal {
Decimal::new(self.frac_part() as i64, PRECISION as u32)
}
#[cfg(feature = "bigdecimal")]
pub fn into_bigdecimal(self) -> bigdecimal::BigDecimal {
bigdecimal::BigDecimal::from_bigint(bigdecimal::num_bigint::BigInt::from_u128(self.raw).unwrap(), Self::MAX_SCALE.into())
}
pub fn into_decimal(self) -> Decimal {
self.int_part_decimal() + self.frac_part_decimal()
}
pub const fn wrapping_add(self, rhs: Self) -> Self {
Self::new_const(self.raw.wrapping_add(rhs.raw), None)
}
pub const fn wrapping_sub(self, rhs: Self) -> Self {
Self::new_const(self.raw.wrapping_sub(rhs.raw), None)
}
pub const fn saturating_add(self, rhs: Self) -> Self {
Self::new_const(self.raw.saturating_add(rhs.raw), None)
}
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self::new_const(self.raw.saturating_sub(rhs.raw), None)
}
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
match self.raw.checked_add(rhs.raw) {
Some(raw) => Some(Self::new_const(raw, None)),
None => None,
}
}
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
match self.raw.checked_sub(rhs.raw) {
Some(raw) => Some(Self::new_const(raw, None)),
None => None,
}
}
pub fn with_precision<const NEW_PRECISION: u8>(self) -> Fixed128<NEW_PRECISION> {
if PRECISION >= NEW_PRECISION {
let factor = const { 10u128.pow(PRECISION.saturating_sub(NEW_PRECISION) as u32) };
let scale_override = self.scale_override.map(|scale| scale.min(NEW_PRECISION));
return Fixed128::<NEW_PRECISION>::new_with_scale(self.raw / factor, scale_override);
}
let factor = const { 10u128.pow(NEW_PRECISION.saturating_sub(PRECISION) as u32) };
Fixed128::<NEW_PRECISION>::new_with_scale(self.raw * factor, self.scale_override)
}
pub fn with_checked_precision<const NEW_PRECISION: u8>(self) -> Option<Fixed128<NEW_PRECISION>> {
if PRECISION >= NEW_PRECISION {
if self.minimal_scale() > NEW_PRECISION {
return None;
}
let factor = const { 10u128.pow(PRECISION.saturating_sub(NEW_PRECISION) as u32) };
let scale_override = self.scale_override.map(|scale| scale.min(NEW_PRECISION));
return Some(Fixed128::<NEW_PRECISION>::new_with_scale(self.raw / factor, scale_override));
}
Some(Fixed128::<NEW_PRECISION>::new_with_scale(
self.raw * const { 10u128.pow(NEW_PRECISION.saturating_sub(PRECISION) as u32) },
self.scale_override,
))
}
pub fn from_str_lossy(s: &str) -> Result<Self, FixedParseErr> {
if let Some((int, frac)) = s.split_once('.') {
let mut frac = frac.to_owned();
let original_frac_len = frac.len().min(PRECISION as usize);
let precision_diff = (PRECISION as isize) - (frac.len() as isize);
if precision_diff > 0 {
frac.push_str(&"0".repeat(precision_diff as usize));
} else {
frac = frac[..PRECISION as usize].to_owned();
}
let int = if int.is_empty() { 0 } else { int.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)? };
let frac = if frac.is_empty() { 0 } else { frac.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)? };
if int > const { u128::MAX / Self::ONE.raw } {
return Err(FixedParseErr::TooLarge);
}
let raw = int * Self::ONE.raw + frac;
return Ok(Self::new_with_scale(raw, Some(original_frac_len as u8)));
}
let int = s.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)?;
if int > const { u128::MAX / Self::ONE.raw } {
return Err(FixedParseErr::TooLarge);
}
Ok(Self::from_int(int))
}
pub fn div_raw(self, rhs: Self) -> Fixed256<PRECISION> {
let a = U256::from(self.raw) * U256::from(Self::ONE.raw);
let b = U256::from(rhs.raw);
Fixed256(a / b)
}
pub fn div_saturating(self, rhs: Self) -> Self {
self.div_raw(rhs).saturating_x128()
}
pub fn div_checked(self, rhs: Self) -> Option<Self> {
self.div_raw(rhs).checked_x128()
}
pub fn mul_raw(self, rhs: Self) -> Fixed256<PRECISION> {
let a = U256::from(self.raw);
let b = U256::from(rhs.raw);
let x = (a / b) / U256::from(Self::ONE.raw);
Fixed256(x)
}
}
#[cfg(feature = "schema")]
impl<const PRECISION: u8> schemars::JsonSchema for Fixed128<PRECISION> {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Fixed128".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
generator.clone().into_root_schema_for::<rust_decimal::Decimal>()
}
}
impl<const PRECISION: u8> std::fmt::Display for Fixed128<PRECISION> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let int = self.int_part();
let mut frac = self.frac_part().to_string();
frac.insert_str(0, &"0".repeat((PRECISION as usize).saturating_sub(frac.len())));
let frac = frac.trim_end_matches('0');
if frac.is_empty() {
return write!(f, "{int}");
}
write!(f, "{int}.{frac}")
}
}
impl<const PRECISION: u8> std::fmt::Debug for Fixed128<PRECISION> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
#[derive(Debug, thiserror::Error)]
pub enum FixedParseErr {
#[error("Unsupported chars")]
InvalidChars,
#[error("Fixed128 will not support numbers so large")]
TooLarge,
#[error("Not enough precision")]
Loss,
}
impl<const PRECISION: u8> FromStr for Fixed128<PRECISION> {
type Err = FixedParseErr;
#[allow(clippy::comparison_chain)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((int, frac)) = s.split_once('.') {
let mut frac = frac.to_owned();
let original_frac_len = frac.len();
let precision_diff = (PRECISION as isize) - (frac.len() as isize);
if precision_diff > 0 {
frac.push_str(&"0".repeat(precision_diff as usize));
} else if precision_diff < 0 {
return Err(FixedParseErr::Loss);
}
let int = if int.is_empty() { 0 } else { int.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)? };
let frac = if frac.is_empty() { 0 } else { frac.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)? };
if int > const { u128::MAX / Self::ONE.raw } {
return Err(FixedParseErr::TooLarge);
}
let raw = int * Self::ONE.raw + frac;
return Ok(Self::new_with_scale(raw, Some((original_frac_len as u8).min(PRECISION))));
}
let int = s.parse::<u128>().map_err(|_| FixedParseErr::InvalidChars)?;
if int > const { u128::MAX / Self::ONE.raw } {
return Err(FixedParseErr::TooLarge);
}
Ok(Self::from_int(int))
}
}
impl<const PRECISION: u8> std::ops::Add for Fixed128<PRECISION> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new_with_scale(self.raw + rhs.raw, None)
}
}
impl<const PRECISION: u8> std::ops::AddAssign for Fixed128<PRECISION> {
fn add_assign(&mut self, rhs: Self) {
self.raw += rhs.raw;
self.scale_override = None;
}
}
impl<const PRECISION: u8> std::ops::Sub for Fixed128<PRECISION> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self::new_with_scale(self.raw - rhs.raw, None)
}
}
impl<const PRECISION: u8> std::ops::SubAssign for Fixed128<PRECISION> {
fn sub_assign(&mut self, rhs: Self) {
self.raw -= rhs.raw;
self.scale_override = None;
}
}
impl<const PRECISION: u8> std::ops::Mul for Fixed128<PRECISION> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let a = U256::from(self.raw);
let b = U256::from(rhs.raw);
Self::new_with_scale(((a * b) / U256::from(Self::ONE.raw)).low_u128(), None)
}
}
impl<const PRECISION: u8> std::ops::MulAssign for Fixed128<PRECISION> {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<const PRECISION: u8> std::ops::Mul<u64> for Fixed128<PRECISION> {
type Output = Self;
fn mul(self, rhs: u64) -> Self::Output {
Self::new_with_scale(self.raw * rhs as u128, None)
}
}
impl<const PRECISION: u8> std::ops::MulAssign<u64> for Fixed128<PRECISION> {
fn mul_assign(&mut self, rhs: u64) {
self.raw *= rhs as u128;
self.scale_override = None;
}
}
impl<const PRECISION: u8> std::ops::Div<u64> for Fixed128<PRECISION> {
type Output = Self;
fn div(self, rhs: u64) -> Self::Output {
Self::new_with_scale(self.raw / rhs as u128, None)
}
}
impl<const PRECISION: u8> std::ops::DivAssign<u64> for Fixed128<PRECISION> {
fn div_assign(&mut self, rhs: u64) {
self.raw /= rhs as u128;
self.scale_override = None;
}
}
impl<const PRECISION: u8> std::ops::Div<Fixed128<PRECISION>> for Fixed128<PRECISION> {
type Output = Self;
fn div(self, rhs: Fixed128<PRECISION>) -> Self::Output {
self.div_raw(rhs).checked_x128().unwrap()
}
}
impl<const PRECISION: u8> std::ops::DivAssign<Fixed128<PRECISION>> for Fixed128<PRECISION> {
fn div_assign(&mut self, rhs: Fixed128<PRECISION>) {
*self = *self / rhs;
}
}
impl<const PRECISION: u8> serde::Serialize for Fixed128<PRECISION> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de, const PRECISION: u8> serde::Deserialize<'de> for Fixed128<PRECISION> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?.parse::<Self>().map_err(serde::de::Error::custom)
}
}
impl<const PRECISION: u8> num_traits::Zero for Fixed128<PRECISION> {
fn zero() -> Self {
Self::ZERO
}
fn is_zero(&self) -> bool {
self.raw.is_zero()
}
}
impl<const PRECISION: u8> From<u128> for Fixed128<PRECISION> {
fn from(value: u128) -> Self {
Self::from_int(value)
}
}
impl<const PRECISION: u8> From<u64> for Fixed128<PRECISION> {
fn from(value: u64) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<u32> for Fixed128<PRECISION> {
fn from(value: u32) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<u16> for Fixed128<PRECISION> {
fn from(value: u16) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<u8> for Fixed128<PRECISION> {
fn from(value: u8) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<i128> for Fixed128<PRECISION> {
fn from(value: i128) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<i64> for Fixed128<PRECISION> {
fn from(value: i64) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<i32> for Fixed128<PRECISION> {
fn from(value: i32) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<i16> for Fixed128<PRECISION> {
fn from(value: i16) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<i8> for Fixed128<PRECISION> {
fn from(value: i8) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<bool> for Fixed128<PRECISION> {
fn from(value: bool) -> Self {
Self::from_int(value as u128)
}
}
impl<const PRECISION: u8> From<f64> for Fixed128<PRECISION> {
fn from(value: f64) -> Self {
Self::from_float(value)
}
}
impl<const PRECISION: u8> From<f32> for Fixed128<PRECISION> {
fn from(value: f32) -> Self {
Self::from_float(value as f64)
}
}
impl<const PRECISION: u8> From<rust_decimal::Decimal> for Fixed128<PRECISION> {
fn from(value: rust_decimal::Decimal) -> Self {
Self::from_decimal(value)
}
}
impl<const PRECISION: u8> std::hash::Hash for Fixed128<PRECISION> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.raw.hash(state);
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::Fixed128;
#[test]
fn preserves_declared_scale_for_zero_fraction() {
let value = Fixed128::<3>::from_str("0.000").expect("parse 0.000");
assert_eq!(value.scale(), 3);
}
#[test]
fn zero_without_fraction_stays_scale_zero() {
let value = Fixed128::<3>::from_str("0").expect("parse 0");
assert_eq!(value.scale(), 0);
}
#[test]
fn zero_with_fraction_allows_downscale_checked() {
let value = Fixed128::<3>::from_str("0.000").unwrap();
let downscaled = value.with_checked_precision::<1>().expect("downscale succeeds");
assert_eq!(downscaled.scale(), 1);
assert_eq!(downscaled.into_raw(), 0);
}
#[test]
fn equality_ignores_scale_override() {
let zero_plain = Fixed128::<3>::from_str("0").unwrap();
let zero_fraction = Fixed128::<3>::from_str("0.000").unwrap();
assert_eq!(zero_plain, zero_fraction);
}
}