use fastnum::D128;
use fastnum::decimal::Context;
use crate::{Error, ErrorKind, Result};
pub type Decimal = D128;
pub fn decimal_from_i128_with_scale(mantissa: i128, scale: u32) -> Decimal {
if scale == 0 {
return D128::from_i128(mantissa).expect("i128 always fits in D128");
}
let is_negative = mantissa < 0;
let abs_str = mantissa.unsigned_abs().to_string();
let scale_usize = scale as usize;
let decimal_str = if abs_str.len() <= scale_usize {
let zeros_needed = scale_usize - abs_str.len();
format!(
"{}0.{}{}",
if is_negative { "-" } else { "" },
"0".repeat(zeros_needed),
abs_str
)
} else {
let decimal_pos = abs_str.len() - scale_usize;
format!(
"{}{}.{}",
if is_negative { "-" } else { "" },
&abs_str[..decimal_pos],
&abs_str[decimal_pos..]
)
};
D128::from_str(&decimal_str, Context::default())
.expect("constructed decimal string is always valid")
}
pub fn try_decimal_from_i128_with_scale(mantissa: i128, scale: u32) -> Result<Decimal> {
Ok(decimal_from_i128_with_scale(mantissa, scale))
}
#[allow(dead_code)]
pub fn decimal_new(mantissa: i64, scale: u32) -> Decimal {
decimal_from_i128_with_scale(mantissa as i128, scale)
}
pub fn decimal_from_str_exact(s: &str) -> Result<Decimal> {
D128::from_str(s, Context::default())
.map_err(|e| Error::new(ErrorKind::DataInvalid, format!("Can't parse decimal: {e}")))
}
pub fn decimal_mantissa(d: &Decimal) -> i128 {
let digits = d.digits();
let unsigned: u128 = digits
.to_u128()
.expect("Iceberg decimals (max 38 digits) always fit in u128");
let signed = unsigned as i128;
if d.is_sign_negative() {
-signed
} else {
signed
}
}
pub fn decimal_scale(d: &Decimal) -> u32 {
let frac = d.fractional_digits_count();
if frac < 0 { 0 } else { frac as u32 }
}
pub fn decimal_rescale(d: Decimal, scale: u32) -> Decimal {
d.rescale(scale as i16)
}
pub fn i128_from_be_bytes(bytes: &[u8]) -> Option<i128> {
if bytes.is_empty() {
return Some(0);
}
if bytes.len() > 16 {
return None; }
let is_negative = bytes[0] & 0x80 != 0;
let mut padded = if is_negative { [0xFF; 16] } else { [0; 16] };
let start = 16 - bytes.len();
padded[start..].copy_from_slice(bytes);
Some(i128::from_be_bytes(padded))
}
pub fn i128_to_be_bytes_min(value: i128) -> Vec<u8> {
let bytes = value.to_be_bytes();
let is_negative = value < 0;
let skip_byte = if is_negative { 0xFF } else { 0x00 };
let mut start = 0;
while start < 15 && bytes[start] == skip_byte {
let next_byte = bytes[start + 1];
let next_is_negative = (next_byte & 0x80) != 0;
if next_is_negative == is_negative {
start += 1;
} else {
break;
}
}
bytes[start..].to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decimal_from_i128_with_scale() {
let d = decimal_from_i128_with_scale(12345, 2);
assert_eq!(d.to_string(), "123.45");
let d = decimal_from_i128_with_scale(-12345, 2);
assert_eq!(d.to_string(), "-123.45");
let d = decimal_from_i128_with_scale(0, 5);
assert_eq!(d.to_string(), "0.00000");
}
#[test]
fn test_decimal_new() {
let d = decimal_new(123, 2);
assert_eq!(d.to_string(), "1.23");
let d = decimal_new(-456, 3);
assert_eq!(d.to_string(), "-0.456");
}
#[test]
fn test_decimal_from_str_exact() {
let d = decimal_from_str_exact("123.45").unwrap();
assert_eq!(d.to_string(), "123.45");
let d = decimal_from_str_exact("-0.001").unwrap();
assert_eq!(d.to_string(), "-0.001");
let d = decimal_from_str_exact("99999999999999999999999999999999999999").unwrap();
assert_eq!(d.to_string(), "99999999999999999999999999999999999999");
}
#[test]
fn test_decimal_mantissa() {
let d = decimal_from_i128_with_scale(12345, 2);
assert_eq!(decimal_mantissa(&d), 12345);
let d = decimal_from_i128_with_scale(-12345, 2);
assert_eq!(decimal_mantissa(&d), -12345);
}
#[test]
fn test_decimal_scale() {
let d = decimal_from_i128_with_scale(12345, 2);
assert_eq!(decimal_scale(&d), 2);
let d = decimal_from_i128_with_scale(12345, 0);
assert_eq!(decimal_scale(&d), 0);
}
#[test]
fn test_decimal_rescale() {
let d = decimal_from_str_exact("123.45").unwrap();
let rescaled = decimal_rescale(d, 4);
assert_eq!(decimal_scale(&rescaled), 4);
assert_eq!(decimal_mantissa(&rescaled), 1234500);
}
#[test]
fn test_38_digit_precision() {
let max_38_digits = "99999999999999999999999999999999999999";
let d = decimal_from_str_exact(max_38_digits).unwrap();
assert_eq!(d.to_string(), max_38_digits);
let min_38_digits = "-99999999999999999999999999999999999999";
let d = decimal_from_str_exact(min_38_digits).unwrap();
assert_eq!(d.to_string(), min_38_digits);
}
#[test]
fn test_i128_from_be_bytes() {
assert_eq!(i128_from_be_bytes(&[]), Some(0));
assert_eq!(i128_from_be_bytes(&[0x01]), Some(1));
assert_eq!(i128_from_be_bytes(&[0x7F]), Some(127));
assert_eq!(i128_from_be_bytes(&[0x00, 0xFF]), Some(255));
assert_eq!(i128_from_be_bytes(&[0x04, 0xD2]), Some(1234));
assert_eq!(i128_from_be_bytes(&[0xFF]), Some(-1));
assert_eq!(i128_from_be_bytes(&[0x80]), Some(-128));
assert_eq!(i128_from_be_bytes(&[0xFB, 0x2E]), Some(-1234));
assert_eq!(i128_from_be_bytes(&[0; 17]), None);
}
#[test]
fn test_i128_to_be_bytes_min() {
assert_eq!(i128_to_be_bytes_min(0), vec![0x00]);
assert_eq!(i128_to_be_bytes_min(1), vec![0x01]);
assert_eq!(i128_to_be_bytes_min(127), vec![0x7F]);
assert_eq!(i128_to_be_bytes_min(128), vec![0x00, 0x80]);
assert_eq!(i128_to_be_bytes_min(255), vec![0x00, 0xFF]);
assert_eq!(i128_to_be_bytes_min(1234), vec![0x04, 0xD2]);
assert_eq!(i128_to_be_bytes_min(-1), vec![0xFF]);
assert_eq!(i128_to_be_bytes_min(-128), vec![0x80]);
assert_eq!(i128_to_be_bytes_min(-129), vec![0xFF, 0x7F]);
assert_eq!(i128_to_be_bytes_min(-1234), vec![0xFB, 0x2E]);
for val in [
0i128,
1,
-1,
127,
-128,
255,
-256,
12345,
-12345,
i128::MAX,
i128::MIN,
] {
let bytes = i128_to_be_bytes_min(val);
assert_eq!(
i128_from_be_bytes(&bytes),
Some(val),
"Round trip failed for {val}"
);
}
}
}