Skip to content

Commit 7977402

Browse files
started on decimal32 implementation using bit ops
1 parent d1d73c0 commit 7977402

File tree

4 files changed

+157
-7
lines changed

4 files changed

+157
-7
lines changed

amqp-type/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ pub enum AppError {
88
DeserializationIllegalConstructorError(u8),
99
#[error("Iterator was empty or too short.")]
1010
IteratorEmptyOrTooShortError,
11+
#[error("Error while converting Decimal32.")]
12+
Decimal32ConversionError(#[from] crate::fixed_width::decimal32::Decimal32ConversionError),
13+
#[error("Error while converting Decimal64.")]
14+
Decimal64ConversionError(#[from] crate::fixed_width::decimal64::Decimal64ConversionError),
15+
#[error("Error while converting Decimal128.")]
16+
Decimal128ConversionError(#[from] crate::fixed_width::decimal128::Decimal128ConversionError),
1117
}

amqp-type/src/fixed_width/decimal128.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ impl From<f64> for Decimal128 {
1717
}
1818
}
1919

20+
#[derive(thiserror::Error, Debug)]
21+
pub enum Decimal128ConversionError {
22+
#[error("Coefficient is too large for Decimal128 representation.")]
23+
CoefficientTooLarge,
24+
#[error("Exponent overflowed in Decimal128 representation")]
25+
ExponentOverflow,
26+
#[error("Exponent underflowed in Decimal128 representation")]
27+
ExponentUnderflow,
28+
}
29+
2030
#[cfg(test)]
2131
mod test {
2232
use super::*;
Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,151 @@
1-
use bigdecimal::BigDecimal;
2-
31
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;
412

513
#[derive(Hash, Eq, PartialEq)]
614
pub struct Decimal32(BigDecimal);
715

816
impl Encode for Decimal32 {
917
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);
11114
}
115+
Ok(())
12116
}
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);
16121
}
122+
Ok(())
17123
}
124+
18125
#[cfg(test)]
19126
mod test {
127+
use bigdecimal::num_traits::ToBytes;
128+
20129
use super::*;
21130

22131
#[test]
23132
fn construct_decimal_32() {
24-
let val: Decimal32 = 32.0.into();
133+
let val: Decimal32 = 32.0.try_into().unwrap();
25134
assert_eq!(val.encode().constructor(), 0x74);
26135
}
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+
}
27151
}

amqp-type/src/fixed_width/decimal64.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ impl From<f64> for Decimal64 {
1717
}
1818
}
1919

20+
#[derive(thiserror::Error, Debug)]
21+
pub enum Decimal64ConversionError {
22+
#[error("Coefficient is too large for Decimal64 representation.")]
23+
CoefficientTooLarge,
24+
#[error("Exponent overflowed in Decimal64 representation")]
25+
ExponentOverflow,
26+
#[error("Exponent underflowed in Decimal64 representation")]
27+
ExponentUnderflow,
28+
}
29+
2030
#[cfg(test)]
2131
mod test {
2232
use super::*;

0 commit comments

Comments
 (0)