|
1 |
| -use bigdecimal::BigDecimal; |
2 |
| - |
3 | 1 | use crate::serde::encode::{Encode, Encoded};
|
| 2 | +use bigdecimal::{ |
| 3 | + num_bigint::{BigInt, Sign}, |
| 4 | + BigDecimal, Signed, Zero, |
| 5 | +}; |
| 6 | + |
| 7 | +const EXPONENT_BIAS: i64 = 101; |
| 8 | +const EXPONENT_MAX: i64 = 96; |
| 9 | +const EXPONENT_MIN: i64 = -95; |
| 10 | +const COEFFICIENT_MAX: i64 = 9_999_999; // 7 digits |
| 11 | +const DEFAULT_CONSTR: u8 = 0x74; |
4 | 12 |
|
5 | 13 | #[derive(Hash, Eq, PartialEq)]
|
6 | 14 | pub struct Decimal32(BigDecimal);
|
7 | 15 |
|
8 | 16 | impl Encode for Decimal32 {
|
9 | 17 | fn encode(&self) -> Encoded {
|
10 |
| - 0x74.into() |
| 18 | + Encoded::new_fixed( |
| 19 | + DEFAULT_CONSTR, |
| 20 | + encode_to_bytes(&self.0).unwrap(), |
| 21 | + ) |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +impl TryFrom<f32> for Decimal32 { |
| 26 | + type Error = Decimal32ConversionError; |
| 27 | + |
| 28 | + fn try_from(value: f32) -> Result<Self, Self::Error> { |
| 29 | + Ok(Decimal32(BigDecimal::try_from(value)?)) |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +#[derive(thiserror::Error, Debug)] |
| 34 | +pub enum Decimal32ConversionError { |
| 35 | + #[error("Failed to parse f32 value to Decimal32 value.")] |
| 36 | + ParseDecimal32Error(#[from] bigdecimal::ParseBigDecimalError), |
| 37 | + #[error("Coefficient is too large for Decimal32 representation.")] |
| 38 | + CoefficientTooLarge, |
| 39 | + #[error("Exponent overflowed in Decimal32 representation")] |
| 40 | + ExponentOverflow, |
| 41 | + #[error("Exponent underflowed in Decimal32 representation")] |
| 42 | + ExponentUnderflow, |
| 43 | + #[error("Failed to scale coefficient. Value cannot be fit into 32 bits.")] |
| 44 | + CoefficientScalingFailedError, |
| 45 | + #[error("The base value for setting the sign for converting the Decimal32 into bytes must be zero.")] |
| 46 | + SignSettingValueIsNotZero, |
| 47 | +} |
| 48 | + |
| 49 | +type ConversionError = Decimal32ConversionError; |
| 50 | + |
| 51 | +fn encode_to_bytes(value: &BigDecimal) -> Result<Vec<u8>, Decimal32ConversionError> { |
| 52 | + if value.is_zero() { |
| 53 | + return Ok([0; 4].to_vec()); |
| 54 | + } |
| 55 | + |
| 56 | + // start with empty bit array of 32 bits |
| 57 | + let result: u32 = 0; |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + let (mut coeff, mut exp) = value.as_bigint_and_exponent(); |
| 62 | + verify_coefficient_not_too_large(&coeff)?; |
| 63 | + verify_exponent_below_max(&exp)?; |
| 64 | + let sign_bit = match coeff.sign() { |
| 65 | + Sign::Minus => 0x80, |
| 66 | + _ => 0, |
| 67 | + }; |
| 68 | + |
| 69 | + let biased_exp = (exp + EXPONENT_BIAS) as u8; |
| 70 | + let coeff_bytes = coeff.abs().to_bytes_be().1; |
| 71 | + |
| 72 | + verify_coefficient_scaling(&coeff_bytes)?; |
| 73 | + |
| 74 | + let mut result: [u8; 4] = [sign_bit | biased_exp, 0, 0, 0]; |
| 75 | + let offset = 4 - coeff_bytes.len(); |
| 76 | + for (i, &byte) in coeff_bytes.iter().enumerate() { |
| 77 | + result[offset + i] = byte; |
| 78 | + } |
| 79 | + |
| 80 | + Ok(result.to_vec()) |
| 81 | +} |
| 82 | + |
| 83 | +fn set_sign_bit(mut result: u32, sign: Sign) -> Result<u32, ConversionError> { |
| 84 | + if result != 0 { |
| 85 | + return Err(Decimal32ConversionError::SignSettingValueIsNotZero); |
| 86 | + } |
| 87 | + match sign { |
| 88 | + Sign::Minus => { |
| 89 | + result += 1; // set bit as least significant |
| 90 | + result <<= 31; // shift bit to sign bit location |
| 91 | + Ok(result) |
| 92 | + } |
| 93 | + _ => Ok(result) |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +fn verify_coefficient_not_too_large(coeff: &BigInt) -> Result<(), ConversionError> { |
| 98 | + if coeff.abs() > COEFFICIENT_MAX.into() { |
| 99 | + return Err(Decimal32ConversionError::CoefficientTooLarge); |
| 100 | + } |
| 101 | + Ok(()) |
| 102 | +} |
| 103 | + |
| 104 | +fn verify_exponent_below_max(exp: &i64) -> Result<(), Decimal32ConversionError> { |
| 105 | + if exp > &EXPONENT_MAX { |
| 106 | + return Err(Decimal32ConversionError::ExponentOverflow); |
| 107 | + } |
| 108 | + Ok(()) |
| 109 | +} |
| 110 | + |
| 111 | +fn verify_no_exponent_underflow(exp: &i64) -> Result<(), Decimal32ConversionError> { |
| 112 | + if exp < &EXPONENT_MIN { |
| 113 | + return Err(Decimal32ConversionError::ExponentUnderflow); |
11 | 114 | }
|
| 115 | + Ok(()) |
12 | 116 | }
|
13 |
| -impl From<f32> for Decimal32 { |
14 |
| - fn from(value: f32) -> Self { |
15 |
| - Decimal32(BigDecimal::try_from(value).unwrap()) |
| 117 | + |
| 118 | +fn verify_coefficient_scaling(coeff_bytes: &Vec<u8>) -> Result<(), Decimal32ConversionError> { |
| 119 | + if coeff_bytes.len() > 3 { |
| 120 | + return Err(Decimal32ConversionError::CoefficientScalingFailedError); |
16 | 121 | }
|
| 122 | + Ok(()) |
17 | 123 | }
|
| 124 | + |
18 | 125 | #[cfg(test)]
|
19 | 126 | mod test {
|
| 127 | + use bigdecimal::num_traits::ToBytes; |
| 128 | + |
20 | 129 | use super::*;
|
21 | 130 |
|
22 | 131 | #[test]
|
23 | 132 | fn construct_decimal_32() {
|
24 |
| - let val: Decimal32 = 32.0.into(); |
| 133 | + let val: Decimal32 = 32.0.try_into().unwrap(); |
25 | 134 | assert_eq!(val.encode().constructor(), 0x74);
|
26 | 135 | }
|
| 136 | + |
| 137 | + #[test] |
| 138 | + fn set_sign_bit_works_for_positive_sign() { |
| 139 | + assert_eq!(set_sign_bit(0, Sign::Plus).unwrap().to_be_bytes(), [0x00, 0x00, 0x00, 0x00]); |
| 140 | + } |
| 141 | + |
| 142 | + #[test] |
| 143 | + fn set_sign_bit_works_for_negative_sign() { |
| 144 | + assert_eq!(set_sign_bit(0, Sign::Minus).unwrap().to_be_bytes(), [0x80, 0x00, 0x00, 0x00]); |
| 145 | + } |
| 146 | + |
| 147 | + #[test] |
| 148 | + fn set_sign_bit_resturns_error_on_non_zero_base_number() { |
| 149 | + assert!(set_sign_bit(4, Sign::Minus).is_err()); |
| 150 | + } |
27 | 151 | }
|
0 commit comments