use std::io::Write;
use arrayvec::ArrayVec;
use bigdecimal::Zero;
use crate::ion_eq::IonEq;
use crate::{
binary::{
int::DecodedInt, raw_binary_writer::MAX_INLINE_LENGTH, var_int::VarInt, var_uint::VarUInt,
},
result::IonResult,
types::{
coefficient::{Coefficient, Sign},
decimal::Decimal,
integer::UInteger,
},
IonError,
};
const DECIMAL_BUFFER_SIZE: usize = 32;
const DECIMAL_POSITIVE_ZERO: Decimal = Decimal {
coefficient: Coefficient {
sign: Sign::Positive,
magnitude: UInteger::U64(0),
},
exponent: 0,
};
pub trait DecimalBinaryEncoder {
fn encode_decimal(&mut self, decimal: &Decimal) -> IonResult<usize>;
fn encode_decimal_value(&mut self, decimal: &Decimal) -> IonResult<usize>;
}
impl<W> DecimalBinaryEncoder for W
where
W: Write,
{
fn encode_decimal(&mut self, decimal: &Decimal) -> IonResult<usize> {
if decimal.ion_eq(&DECIMAL_POSITIVE_ZERO) {
return Ok(0);
}
let mut bytes_written: usize = 0;
bytes_written += VarInt::write_i64(self, decimal.exponent)?;
if decimal.coefficient.is_negative_zero() {
bytes_written += DecodedInt::write_negative_zero(self)?;
return Ok(bytes_written);
}
if let Some(small_coefficient) = decimal.coefficient.as_i64() {
if !small_coefficient.is_zero() {
bytes_written += DecodedInt::write_i64(self, small_coefficient)?;
}
} else {
let mut coefficient_bytes = match decimal.coefficient.magnitude() {
UInteger::U64(unsigned) => unsigned.to_be_bytes().into(),
UInteger::BigUInt(big) => big.to_bytes_be(),
};
let first_byte: &mut u8 = &mut coefficient_bytes[0];
let first_bit_is_zero: bool = *first_byte & 0b1000_0000 == 0;
if let Sign::Negative = decimal.coefficient.sign() {
if first_bit_is_zero {
*first_byte |= 0b1000_0000;
} else {
self.write_all(&[0b1000_0000])?;
bytes_written += 1;
}
} else {
if first_bit_is_zero {
} else {
self.write_all(&[0b0000_0000])?;
bytes_written += 1;
}
}
self.write_all(coefficient_bytes.as_slice())?;
bytes_written += coefficient_bytes.len();
}
Ok(bytes_written)
}
fn encode_decimal_value(&mut self, decimal: &Decimal) -> IonResult<usize> {
let mut bytes_written: usize = 0;
let mut encoded: ArrayVec<u8, DECIMAL_BUFFER_SIZE> = ArrayVec::new();
encoded
.encode_decimal(decimal)
.map_err(|_e| IonError::EncodingError {
description: "found a decimal that was too large for the configured buffer"
.to_string(),
})?;
let type_descriptor: u8;
if encoded.len() <= MAX_INLINE_LENGTH {
type_descriptor = 0x50 | encoded.len() as u8;
self.write_all(&[type_descriptor])?;
bytes_written += 1;
} else {
type_descriptor = 0x5E;
self.write_all(&[type_descriptor])?;
bytes_written += 1;
bytes_written += VarUInt::write_u64(self, encoded.len() as u64)?;
}
self.write_all(&encoded[..])?;
bytes_written += encoded.len();
Ok(bytes_written)
}
}
#[cfg(test)]
mod binary_decimal_tests {
use num_bigint::BigUint;
use rstest::*;
use std::str::FromStr;
use super::*;
#[test]
fn decimal_0d0_is_a_special_zero_value() {
assert_eq!(DECIMAL_POSITIVE_ZERO, Decimal::new(0, 0));
assert!(DECIMAL_POSITIVE_ZERO.ion_eq(&Decimal::new(0, 0)));
assert_eq!(DECIMAL_POSITIVE_ZERO, Decimal::new(0, 10));
assert!(!DECIMAL_POSITIVE_ZERO.ion_eq(&Decimal::new(0, 10)));
assert_eq!(DECIMAL_POSITIVE_ZERO, Decimal::new(0, 100));
assert!(!DECIMAL_POSITIVE_ZERO.ion_eq(&Decimal::new(0, 100)));
}
#[rstest]
#[case::exactly_zero(Decimal::new(0, 0), 1)]
#[case::zero_with_nonzero_exp(Decimal::new(0, 10), 2)]
#[case::meaning_of_life(Decimal::new(42, 0), 3)]
fn bytes_written(#[case] input: Decimal, #[case] expected: usize) -> IonResult<()> {
let mut buf = vec![];
let written = buf.encode_decimal_value(&input)?;
assert_eq!(buf.len(), expected);
assert_eq!(written, expected);
Ok(())
}
#[test]
fn oversized_decimal() {
let mut buf = vec![];
let precise_pi = Decimal::new(
BigUint::from_str("31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679").unwrap(),
-99
);
let result = buf.encode_decimal_value(&precise_pi);
assert!(result.is_err());
}
}