Skip to content

Commit f6182aa

Browse files
authored
Merge pull request #114 from silicon-heaven/decimal-reduce-precision
Decimal reduce precision
2 parents 78b3bd1 + 5fff20f commit f6182aa

5 files changed

Lines changed: 45 additions & 14 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ name = "shvproto"
55
description = "Rust implementation of the SHV protocol"
66
license = "MIT"
77
repository = "https://github.com/silicon-heaven/libshvproto-rs"
8-
version = "6.1.8"
8+
version = "6.1.9"
99
edition = "2024"
1010

1111
[dependencies]

src/chainpack.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -576,15 +576,10 @@ where
576576
));
577577
}
578578

579-
if !(-(1i64 << 55)..(1i64 << 55)).contains(&mantissa) {
580-
return Err(self.make_error(
581-
&format!("Decimal mantissa {mantissa} overflows 56-bit field"),
582-
ReadErrorReason::NumericValueOverflow,
583-
));
584-
}
585-
586-
let d = Decimal::new(mantissa, exponent as i8);
587-
Ok(Value::from(d))
579+
Decimal::try_new_with_precision_reduction(mantissa, exponent as i8).map_or_else(|| Err(self.make_error(
580+
&format!("Decimal mantissa {mantissa} overflows 56-bit field"),
581+
ReadErrorReason::NumericValueOverflow,
582+
)), |d| Ok(Value::from(d)))
588583
}
589584

590585
}
@@ -899,7 +894,10 @@ fn test_decimal() {
899894
let dec = crate::decimal::Decimal::new(0, 0);
900895
assert_eq!(hex_chainpack_to_rpcvalue("8C0000").unwrap(), dec.into());
901896
assert_eq!(rpcvalue_to_hex_chainpack(&dec.into()), "8C0000");
902-
assert_eq!(hex_chainpack_to_rpcvalue("8CF400B201AB082063B54F").unwrap_err().msg, "ChainPack read error - Decimal mantissa 50104379941872565 overflows 56-bit field");
897+
let val = hex_chainpack_to_rpcvalue("8CF400B201AB082063B54F").unwrap();
898+
let dec_val = val.as_decimal();
899+
assert_eq!(dec_val.mantissa(), 5_010_437_994_187_256);
900+
assert_eq!(dec_val.exponent(), -14);
903901
assert_eq!(hex_chainpack_to_rpcvalue("8C4FF400B201AB082063B5").unwrap_err().msg, "ChainPack read error - Decimal exponent 50104379941872565 overflows i8");
904902
}
905903

src/cpon.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,8 @@ mod test
736736
assert_eq!(RpcValue::from_cpon("0.123e3").unwrap().as_decimal(), Decimal::new(123, 0));
737737
test_cpon_round_trip("1000000.", Decimal::new(1_000_000, 0));
738738
test_cpon_round_trip("50.03138741402532", Decimal::new(5_003_138_741_402_532, -14));
739-
// We do not support such high precision.
740-
assert!(RpcValue::from_cpon("36.028797018963968").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow)));
739+
// Precision reduction should handle high precision by cutting digits from the right.
740+
assert_eq!(RpcValue::from_cpon("36.028797018963968").unwrap().as_decimal(), Decimal::new(3_602_879_701_896_396, -14));
741741
assert_eq!(RpcValue::from_cpon(r#""foo""#).unwrap().as_str(), "foo");
742742
assert_eq!(RpcValue::from_cpon(r#""ěščřžýáí""#).unwrap().as_str(), "ěščřžýáí");
743743
assert_eq!(RpcValue::from_cpon("b\"foo\tbar\nbaz\"").unwrap().as_blob(), b"foo\tbar\nbaz");

src/decimal.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@ impl Decimal {
1111
n |= i64::from(exponent) & 0xff;
1212
Decimal(n)
1313
}
14+
pub fn try_new_with_precision_reduction(mantissa: i64, exponent: i8) -> Option<Decimal> {
15+
const MANTISSA_MIN: i64 = -(1i64 << 55);
16+
const MANTISSA_MAX: i64 = 1i64 << 55;
17+
18+
let mut m = mantissa;
19+
let mut e = i64::from(exponent);
20+
21+
while !(MANTISSA_MIN..MANTISSA_MAX).contains(&m) {
22+
m /= 10;
23+
e += 1;
24+
25+
if e < i64::from(i8::MIN) || e > i64::from(i8::MAX) {
26+
return None;
27+
}
28+
}
29+
30+
if !(MANTISSA_MIN..MANTISSA_MAX).contains(&m) {
31+
return None;
32+
}
33+
34+
Some(Decimal::new(m, e as i8))
35+
}
1436
#[must_use]
1537
pub fn normalize(self) -> Decimal {
1638
let (mut mantissa, mut exponent) = self.decode();

src/textrdwr.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ pub trait TextReader : Reader {
195195
let mut is_uint = false;
196196
let mut is_negative = false;
197197
let mut decimal_overflow = false;
198+
let mut i64_overflow = false;
198199

199200
let b = self.peek_byte()?;
200201
if b == b'+' {
@@ -207,6 +208,7 @@ pub trait TextReader : Reader {
207208
}
208209

209210
let ReadInt { value, digit_cnt, is_overflow, .. } = self.read_int(0, false)?;
211+
i64_overflow = i64_overflow || is_overflow;
210212
decimal_overflow = decimal_overflow || is_overflow;
211213
if digit_cnt == 0 {
212214
return Err(self.make_error("Number should contain at least one digit.", ReadErrorReason::InvalidCharacter))
@@ -230,9 +232,10 @@ pub trait TextReader : Reader {
230232
is_decimal = true;
231233
self.get_byte()?;
232234
let ReadInt { value, digit_cnt, is_overflow, .. } = self.read_int(mantissa, true)?;
235+
i64_overflow = i64_overflow || is_overflow;
233236
decimal_overflow = decimal_overflow || is_overflow;
234237
mantissa = value;
235-
if mantissa >= 0x80_0000_0000_0000 {
238+
if !(-0x80_0000_0000_0000..0x80_0000_0000_0000).contains(&mantissa) {
236239
decimal_overflow = true;
237240
}
238241
dec_cnt = i64::from(digit_cnt);
@@ -245,6 +248,7 @@ pub trait TextReader : Reader {
245248
is_decimal = true;
246249
self.get_byte()?;
247250
let ReadInt { value, digit_cnt, is_negative, is_overflow } = self.read_int(0, false)?;
251+
i64_overflow = i64_overflow || is_overflow;
248252
decimal_overflow = decimal_overflow || is_overflow;
249253
exponent = value;
250254
if is_negative { exponent = -exponent; }
@@ -259,6 +263,13 @@ pub trait TextReader : Reader {
259263
let mantissa = if is_negative { -mantissa } else { mantissa };
260264
if is_decimal {
261265
if decimal_overflow {
266+
if !i64_overflow {
267+
#[expect(clippy::cast_possible_truncation, reason = "We hope that the new exponent is not big enough to truncate")]
268+
let target_exp = (exponent - dec_cnt) as i8;
269+
if let Some(d) = Decimal::try_new_with_precision_reduction(mantissa, target_exp) {
270+
return Ok(Value::from(d));
271+
}
272+
}
262273
return Err(self.make_error("Not enough precision to read the Decimal", ReadErrorReason::NumericValueOverflow))
263274
}
264275
#[expect(clippy::cast_possible_truncation, reason = "We hope that the new exponent is not big enough to truncate")]

0 commit comments

Comments
 (0)