use super::Decimal;
use crate::model::OutOfRangeError;
impl std::convert::TryFrom<bigdecimal::BigDecimal> for Decimal {
type Error = OutOfRangeError;
fn try_from(dec: bigdecimal::BigDecimal) -> Result<Decimal, Self::Error> {
use num_traits::{ToPrimitive, Zero};
use std::cmp::max;
use std::convert::TryInto;
let mut digits = Vec::new();
let (v, scale) = dec.into_bigint_and_exponent();
let (negative, mut val) = match v.sign() {
num_bigint::Sign::Minus => (true, -v),
num_bigint::Sign::NoSign => (false, v),
num_bigint::Sign::Plus => (false, v),
};
let scale_4digits = if scale < 0 { scale / 4 } else { scale / 4 + 1 };
let pad = scale_4digits * 4 - scale;
if pad > 0 {
val *= 10u16.pow(pad as u32);
}
while !val.is_zero() {
digits.push((&val % 10000u16).to_u16().unwrap());
val /= 10000;
}
digits.reverse();
let decimal_digits = max(0, scale).try_into()?;
let weight = i16::try_from(digits.len() as i64 - scale_4digits - 1)?;
Ok(Decimal {
negative,
weight,
decimal_digits,
digits,
}
.normalize())
}
}
impl From<Decimal> for bigdecimal::BigDecimal {
fn from(v: Decimal) -> bigdecimal::BigDecimal {
(&v).into()
}
}
impl From<&Decimal> for bigdecimal::BigDecimal {
fn from(val: &Decimal) -> bigdecimal::BigDecimal {
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_traits::pow;
use std::cmp::max;
if val.digits.is_empty() {
return BigDecimal::from(0);
}
let mut r = BigInt::from(0);
for &digit in &val.digits {
r *= 10000;
r += digit;
}
let decimal_stored = 4 * max(0, val.digits.len() as i64 - val.weight as i64 - 1) as usize;
let pad = if decimal_stored > 0 {
let pad = decimal_stored as i64 - val.decimal_digits as i64;
match pad {
1.. => {
r /= pow(10, pad as usize);
}
0 => {}
..=-1 => {
r *= pow(10, (-pad) as usize);
}
}
pad
} else {
0
};
let scale = if val.decimal_digits == 0 {
-(val.weight as i64 + 1 - val.digits.len() as i64) * 4 - pad as i64
} else {
if decimal_stored == 0 {
let power =
(val.weight as usize + 1 - val.digits.len()) * 4 + val.decimal_digits as usize;
if power > 0 {
r *= pow(BigInt::from(10), power);
}
}
val.decimal_digits as i64
};
if val.negative {
r = -r;
}
BigDecimal::new(r, scale)
}
}
#[cfg(test)]
mod test {
use super::super::test_helpers::{gen_i64, gen_u64};
use super::Decimal;
use bigdecimal::BigDecimal;
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::convert::TryFrom;
use std::str::FromStr;
#[test]
fn decimal_conversion() -> Result<(), Box<dyn std::error::Error>> {
let x = Decimal::try_from(BigDecimal::from_str("42.00")?)?;
assert_eq!(x.weight, 0);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[42]);
let x = Decimal::try_from(BigDecimal::from_str("42.07")?)?;
assert_eq!(x.weight, 0);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[42, 700]);
let x = Decimal::try_from(BigDecimal::from_str("0.07")?)?;
assert_eq!(x.weight, -1);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[700]);
let x = Decimal::try_from(BigDecimal::from_str("420000.00")?)?;
assert_eq!(x.weight, 1);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[42]);
let x = Decimal::try_from(BigDecimal::from_str("-42.00")?)?;
assert_eq!(x.weight, 0);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[42]);
let x = Decimal::try_from(BigDecimal::from_str("-42.07")?)?;
assert_eq!(x.weight, 0);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[42, 700]);
let x = Decimal::try_from(BigDecimal::from_str("-0.07")?)?;
assert_eq!(x.weight, -1);
assert_eq!(x.decimal_digits, 2);
assert_eq!(x.digits, &[700]);
let x = Decimal::try_from(BigDecimal::from_str(
"10000000000000000000000000000000000000.00000",
)?)?;
assert_eq!(x.digits, &[10]);
assert_eq!(x.weight, 9);
assert_eq!(x.decimal_digits, 5);
let x = Decimal::try_from(BigDecimal::from_str("1e100")?)?;
assert_eq!(x.weight, 25);
assert_eq!(x.decimal_digits, 0);
assert_eq!(x.digits, &[1]);
let x = Decimal::try_from(BigDecimal::from_str(
"-703367234220692490200000000000000000000000000",
)?)?;
assert_eq!(x.weight, 11);
assert_eq!(x.decimal_digits, 0);
assert_eq!(x.digits, &[7, 336, 7234, 2206, 9249, 200]);
let x = Decimal::try_from(BigDecimal::from_str("-7033672342206924902e26")?)?;
assert_eq!(x.weight, 11);
assert_eq!(x.decimal_digits, 0);
assert_eq!(x.digits, &[7, 336, 7234, 2206, 9249, 200]);
let x = Decimal::try_from(BigDecimal::from_str(
"6545218855030988517.14400196897187081925e47",
)?)?;
assert_eq!(x.weight, 16);
assert_eq!(x.decimal_digits, 0);
assert_eq!(
x.digits,
&[65, 4521, 8855, 309, 8851, 7144, 19, 6897, 1870, 8192, 5000]
);
let x = Decimal::try_from(BigDecimal::from_str(
"-260399300000000000000000000000000000000000000.\
000000000007745502260",
)?)?;
assert_eq!(x.weight, 11);
assert_eq!(x.decimal_digits, 21);
assert_eq!(
x.digits,
&[
2, 6039, 9300, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7455, 226,
]
);
Ok(())
}
#[test]
fn convert_special() {
let orig = Decimal {
negative: false,
weight: 0,
decimal_digits: 1,
digits: Vec::new(),
};
let big: BigDecimal = orig.into();
assert_eq!(big.to_string(), "0");
let orig = Decimal {
negative: false,
weight: 0,
decimal_digits: 0,
digits: Vec::new(),
};
let big: BigDecimal = orig.into();
assert_eq!(big.to_string(), "0");
}
fn dec_roundtrip(s: &str) -> BigDecimal {
let rust = BigDecimal::from_str(s).expect("can parse big decimal");
let edgedb = Decimal::try_from(rust).expect("can convert for edgedb");
BigDecimal::from(edgedb)
}
#[test]
fn decimal_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
assert_eq!(dec_roundtrip("1"), B::from_str("1")?);
assert_eq!(dec_roundtrip("1000"), B::from_str("1000")?);
assert_eq!(dec_roundtrip("1e100"), B::from_str("1e100")?);
assert_eq!(dec_roundtrip("0"), B::from_str("0")?);
assert_eq!(dec_roundtrip("-1000"), B::from_str("-1000")?);
assert_eq!(dec_roundtrip("1.01"), B::from_str("1.01")?);
assert_eq!(dec_roundtrip("1000.0070"), B::from_str("1000.0070")?);
assert_eq!(dec_roundtrip("0.00008"), B::from_str("0.00008")?);
assert_eq!(dec_roundtrip("-1000.1"), B::from_str("-1000.1")?);
assert_eq!(
dec_roundtrip("10000000000000000000000000000000000000.00001"),
B::from_str("10000000000000000000000000000000000000.00001")?
);
assert_eq!(
dec_roundtrip("12345678901234567890012345678901234567890123"),
B::from_str("12345678901234567890012345678901234567890123")?
);
assert_eq!(
dec_roundtrip("1234567890123456789.012345678901234567890123"),
B::from_str("1234567890123456789.012345678901234567890123")?
);
assert_eq!(
dec_roundtrip("0.000000000000000000000000000000000000017238"),
B::from_str("0.000000000000000000000000000000000000017238")?
);
assert_eq!(dec_roundtrip("1234.00000"), B::from_str("1234.00000")?);
assert_eq!(
dec_roundtrip("10000000000000000000000000000000000000.00000"),
B::from_str("10000000000000000000000000000000000000.00000")?
);
assert_eq!(
dec_roundtrip("100010001000000000000000000000000000"),
B::from_str("100010001000000000000000000000000000")?
);
Ok(())
}
#[test]
fn decimal_rand_i64() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(1);
for _ in 0..10000 {
let head = gen_u64(&mut rng);
let txt = format!("{}", head);
assert_eq!(dec_roundtrip(&txt), B::from_str(&txt)?, "parsing: {}", txt);
}
Ok(())
}
#[test]
fn decimal_rand_nulls() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(2);
for iter in 0..10000 {
let head = gen_u64(&mut rng);
let nulls = rng.gen_range(0..100);
let txt = format!("{0}{1:0<2$}", head, "", nulls);
assert_eq!(
dec_roundtrip(&txt),
B::from_str(&txt)?,
"parsing {}: {}",
iter,
txt
);
}
Ok(())
}
#[test]
fn decimal_rand_eplus() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(3);
for iter in 0..10000 {
let head = gen_u64(&mut rng);
let nulls = rng.gen_range(-100..100);
let txt = format!("{}e{}", head, nulls);
assert_eq!(
dec_roundtrip(&txt),
B::from_str(&txt)?,
"parsing {}: {}",
iter,
txt
);
}
Ok(())
}
#[test]
fn decimal_rand_fract_eplus() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(4);
for iter in 0..10000 {
let head = gen_i64(&mut rng);
let fract = gen_u64(&mut rng);
let nulls = rng.gen_range(-100..100);
let txt = format!("{}.{}e{}", head, fract, nulls);
let rt = dec_roundtrip(&txt);
let dec = if head == 0 && fract == 0 {
B::from(0)
} else {
B::from_str(&txt)?
};
assert_eq!(rt, dec, "parsing {}: {}", iter, txt);
if dec.as_bigint_and_exponent().1 > 0 {
assert_eq!(
rt.as_bigint_and_exponent().1,
dec.as_bigint_and_exponent().1,
"precision: {}",
txt
);
}
}
Ok(())
}
#[test]
fn decimal_rand_nulls_eplus() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(5);
for iter in 0..10000 {
let head = gen_i64(&mut rng);
let nulls1 = rng.gen_range(0..100);
let nulls2 = rng.gen_range(0..100);
let txt = format!("{0}{1:0<2$}e{3}", head, "", nulls1, nulls2);
let rt = dec_roundtrip(&txt);
let dec = B::from_str(&txt)?;
assert_eq!(rt, dec, "parsing {}: {}", iter, txt);
if dec.as_bigint_and_exponent().1 > 0 {
assert_eq!(
rt.as_bigint_and_exponent().1,
dec.as_bigint_and_exponent().1,
"precision: {}",
txt
);
}
}
Ok(())
}
#[test]
fn decimal_rand_decim() -> Result<(), Box<dyn std::error::Error>> {
use bigdecimal::BigDecimal as B;
let mut rng = StdRng::seed_from_u64(6);
for iter in 0..10000 {
let head = gen_i64(&mut rng);
let nulls1 = rng.gen_range(0..100);
let nulls2 = rng.gen_range(0..100);
let decimals = gen_u64(&mut rng);
let txt = format!(
"{0}{1:0<2$}.{1:0<3$}{4}",
head, "", nulls1, nulls2, decimals
);
let dec = if head == 0 && decimals == 0 {
B::from(0)
} else {
B::from_str(&txt)?
};
assert_eq!(dec_roundtrip(&txt), dec, "parsing {}: {}", iter, txt);
assert_eq!(
dec_roundtrip(&txt).as_bigint_and_exponent().1,
dec.as_bigint_and_exponent().1,
"precision: {}",
txt
);
}
Ok(())
}
}