1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use derive_more::Constructor;
use float_eq::*;
use num_bigint::BigInt;
use std::io::Cursor;
use crate::frame::{Serialize, Version};
/// Cassandra Decimal type
#[derive(Debug, Clone, PartialEq, Constructor, Ord, PartialOrd, Eq, Hash)]
pub struct Decimal {
pub unscaled: BigInt,
pub scale: i32,
}
impl Decimal {
/// Method that returns plain `BigInt` value.
///
/// Negative scale is handled by multiplying instead of dividing - that
/// avoids the previous `scale as u32` cast which made a negative scale
/// wrap to a huge value and panic in `10i64.pow`.
pub fn as_plain(&self) -> BigInt {
if self.scale >= 0 {
// dividing by 10^scale; use checked_pow on a u32 exponent so an
// out-of-range value yields a clean zero rather than panicking.
let exponent = self.scale as u32;
match 10i64.checked_pow(exponent) {
Some(divisor) => self.unscaled.clone() / divisor,
None => BigInt::from(0),
}
} else {
// negative scale means the unscaled value should be multiplied
// by 10^|scale| to recover the represented integer
let exponent = self.scale.unsigned_abs();
self.unscaled.clone() * BigInt::from(10).pow(exponent)
}
}
}
impl Serialize for Decimal {
fn serialize(&self, cursor: &mut Cursor<&mut Vec<u8>>, version: Version) {
self.scale.serialize(cursor, version);
self.unscaled
.to_signed_bytes_be()
.serialize(cursor, version);
}
}
macro_rules! impl_from_for_decimal {
($t:ty) => {
impl From<$t> for Decimal {
fn from(i: $t) -> Self {
Decimal {
unscaled: i.into(),
scale: 0,
}
}
}
};
}
impl_from_for_decimal!(i8);
impl_from_for_decimal!(i16);
impl_from_for_decimal!(i32);
impl_from_for_decimal!(i64);
impl_from_for_decimal!(u8);
impl_from_for_decimal!(u16);
impl From<f32> for Decimal {
fn from(f: f32) -> Decimal {
// Cap the loop just below the point where 10i64.pow(scale) overflows
// (10^19 > i64::MAX). Without this guard a hostile input could keep
// the loop spinning until the pow call panics. In practice f32
// precision causes the equality check to succeed long before this
// cap, so existing well-formed inputs are unaffected.
const MAX_SCALE: u32 = 18;
let mut scale: u32 = 0;
loop {
let unscaled = f * (10i64.pow(scale) as f32);
if float_eq!(unscaled, unscaled.trunc(), abs <= f32::EPSILON) {
return Decimal::new((unscaled as i64).into(), scale as i32);
}
if scale >= MAX_SCALE {
// best-effort termination: snap to the truncated value at the
// current scale rather than looping forever / panicking
return Decimal::new((unscaled.trunc() as i64).into(), scale as i32);
}
scale += 1;
}
}
}
impl From<f64> for Decimal {
fn from(f: f64) -> Decimal {
// Same termination guard as the f32 conversion - bounded just below
// i64 overflow on 10i64.pow.
const MAX_SCALE: u32 = 18;
let mut scale: u32 = 0;
loop {
let unscaled = f * (10i64.pow(scale) as f64);
if float_eq!(unscaled, unscaled.trunc(), abs <= f64::EPSILON) {
return Decimal::new((unscaled as i64).into(), scale as i32);
}
if scale >= MAX_SCALE {
return Decimal::new((unscaled.trunc() as i64).into(), scale as i32);
}
scale += 1;
}
}
}
impl From<Decimal> for BigInt {
fn from(value: Decimal) -> Self {
value.as_plain()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn serialize_test() {
assert_eq!(
Decimal::new(129.into(), 0).serialize_to_vec(Version::V4),
vec![0, 0, 0, 0, 0x00, 0x81]
);
assert_eq!(
Decimal::new(BigInt::from(-129), 0).serialize_to_vec(Version::V4),
vec![0, 0, 0, 0, 0xFF, 0x7F]
);
let expected: Vec<u8> = vec![0, 0, 0, 1, 0x00, 0x81];
assert_eq!(
Decimal::new(129.into(), 1).serialize_to_vec(Version::V4),
expected
);
let expected: Vec<u8> = vec![0, 0, 0, 1, 0xFF, 0x7F];
assert_eq!(
Decimal::new(BigInt::from(-129), 1).serialize_to_vec(Version::V4),
expected
);
}
#[test]
fn from_f32() {
assert_eq!(
Decimal::from(12300001_f32),
Decimal::new(12300001.into(), 0)
);
assert_eq!(
Decimal::from(1230000.1_f32),
Decimal::new(12300001.into(), 1)
);
assert_eq!(
Decimal::from(0.12300001_f32),
Decimal::new(12300001.into(), 8)
);
}
#[test]
fn from_f64() {
assert_eq!(
Decimal::from(1230000000000001_f64),
Decimal::new(1230000000000001i64.into(), 0)
);
assert_eq!(
Decimal::from(123000000000000.1f64),
Decimal::new(1230000000000001i64.into(), 1)
);
assert_eq!(
Decimal::from(0.1230000000000001f64),
Decimal::new(1230000000000001i64.into(), 16)
);
}
// 0.1 is not exactly representable in IEEE-754 float, so the previous
// implementation kept doubling `scale` looking for an exact match and
// eventually panicked on `10i64.pow(scale)` overflow when scale exceeded
// the number of significant digits. The conversion must terminate without
// panicking and produce a sensible Decimal.
#[test]
fn from_f32_tolerates_inexact_floats() {
let _decimal = Decimal::from(0.1f32);
let _decimal = Decimal::from(0.2f32);
let _decimal = Decimal::from(1.0f32 / 3.0f32);
}
#[test]
fn from_f64_tolerates_inexact_floats() {
let _decimal = Decimal::from(0.1f64);
let _decimal = Decimal::from(0.2f64);
let _decimal = Decimal::from(1.0f64 / 3.0f64);
}
// as_plain divides by 10^scale; if scale is negative the previous
// `scale as u32` cast wrapped to a huge value and `10i64.pow` panicked.
// The function should either reject negative scales or handle them.
#[test]
fn as_plain_does_not_panic_on_negative_scale() {
let decimal = Decimal::new(5.into(), -3);
// Just verify it does not panic; we don't assert a specific value
// here because the semantics of negative scale aren't part of this
// bug fix - we only need to be safe.
let _ = decimal.as_plain();
}
}