cassandra_protocol/types/
decimal.rs1use derive_more::Constructor;
2use float_eq::*;
3use num_bigint::BigInt;
4use std::io::Cursor;
5
6use crate::frame::{Serialize, Version};
7
8#[derive(Debug, Clone, PartialEq, Constructor, Ord, PartialOrd, Eq, Hash)]
10pub struct Decimal {
11 pub unscaled: BigInt,
12 pub scale: i32,
13}
14
15impl Decimal {
16 pub fn as_plain(&self) -> BigInt {
22 if self.scale >= 0 {
23 let exponent = self.scale as u32;
26 match 10i64.checked_pow(exponent) {
27 Some(divisor) => self.unscaled.clone() / divisor,
28 None => BigInt::from(0),
29 }
30 } else {
31 let exponent = self.scale.unsigned_abs();
34 self.unscaled.clone() * BigInt::from(10).pow(exponent)
35 }
36 }
37}
38
39impl Serialize for Decimal {
40 fn serialize(&self, cursor: &mut Cursor<&mut Vec<u8>>, version: Version) {
41 self.scale.serialize(cursor, version);
42 self.unscaled
43 .to_signed_bytes_be()
44 .serialize(cursor, version);
45 }
46}
47
48macro_rules! impl_from_for_decimal {
49 ($t:ty) => {
50 impl From<$t> for Decimal {
51 fn from(i: $t) -> Self {
52 Decimal {
53 unscaled: i.into(),
54 scale: 0,
55 }
56 }
57 }
58 };
59}
60
61impl_from_for_decimal!(i8);
62impl_from_for_decimal!(i16);
63impl_from_for_decimal!(i32);
64impl_from_for_decimal!(i64);
65impl_from_for_decimal!(u8);
66impl_from_for_decimal!(u16);
67
68impl From<f32> for Decimal {
69 fn from(f: f32) -> Decimal {
70 const MAX_SCALE: u32 = 18;
76 let mut scale: u32 = 0;
77
78 loop {
79 let unscaled = f * (10i64.pow(scale) as f32);
80
81 if float_eq!(unscaled, unscaled.trunc(), abs <= f32::EPSILON) {
82 return Decimal::new((unscaled as i64).into(), scale as i32);
83 }
84
85 if scale >= MAX_SCALE {
86 return Decimal::new((unscaled.trunc() as i64).into(), scale as i32);
89 }
90
91 scale += 1;
92 }
93 }
94}
95
96impl From<f64> for Decimal {
97 fn from(f: f64) -> Decimal {
98 const MAX_SCALE: u32 = 18;
101 let mut scale: u32 = 0;
102
103 loop {
104 let unscaled = f * (10i64.pow(scale) as f64);
105
106 if float_eq!(unscaled, unscaled.trunc(), abs <= f64::EPSILON) {
107 return Decimal::new((unscaled as i64).into(), scale as i32);
108 }
109
110 if scale >= MAX_SCALE {
111 return Decimal::new((unscaled.trunc() as i64).into(), scale as i32);
112 }
113
114 scale += 1;
115 }
116 }
117}
118
119impl From<Decimal> for BigInt {
120 fn from(value: Decimal) -> Self {
121 value.as_plain()
122 }
123}
124
125#[cfg(test)]
126mod test {
127 use super::*;
128
129 #[test]
130 fn serialize_test() {
131 assert_eq!(
132 Decimal::new(129.into(), 0).serialize_to_vec(Version::V4),
133 vec![0, 0, 0, 0, 0x00, 0x81]
134 );
135
136 assert_eq!(
137 Decimal::new(BigInt::from(-129), 0).serialize_to_vec(Version::V4),
138 vec![0, 0, 0, 0, 0xFF, 0x7F]
139 );
140
141 let expected: Vec<u8> = vec![0, 0, 0, 1, 0x00, 0x81];
142 assert_eq!(
143 Decimal::new(129.into(), 1).serialize_to_vec(Version::V4),
144 expected
145 );
146
147 let expected: Vec<u8> = vec![0, 0, 0, 1, 0xFF, 0x7F];
148 assert_eq!(
149 Decimal::new(BigInt::from(-129), 1).serialize_to_vec(Version::V4),
150 expected
151 );
152 }
153
154 #[test]
155 fn from_f32() {
156 assert_eq!(
157 Decimal::from(12300001_f32),
158 Decimal::new(12300001.into(), 0)
159 );
160 assert_eq!(
161 Decimal::from(1230000.1_f32),
162 Decimal::new(12300001.into(), 1)
163 );
164 assert_eq!(
165 Decimal::from(0.12300001_f32),
166 Decimal::new(12300001.into(), 8)
167 );
168 }
169
170 #[test]
171 fn from_f64() {
172 assert_eq!(
173 Decimal::from(1230000000000001_f64),
174 Decimal::new(1230000000000001i64.into(), 0)
175 );
176 assert_eq!(
177 Decimal::from(123000000000000.1f64),
178 Decimal::new(1230000000000001i64.into(), 1)
179 );
180 assert_eq!(
181 Decimal::from(0.1230000000000001f64),
182 Decimal::new(1230000000000001i64.into(), 16)
183 );
184 }
185
186 #[test]
192 fn from_f32_tolerates_inexact_floats() {
193 let _decimal = Decimal::from(0.1f32);
194 let _decimal = Decimal::from(0.2f32);
195 let _decimal = Decimal::from(1.0f32 / 3.0f32);
196 }
197
198 #[test]
199 fn from_f64_tolerates_inexact_floats() {
200 let _decimal = Decimal::from(0.1f64);
201 let _decimal = Decimal::from(0.2f64);
202 let _decimal = Decimal::from(1.0f64 / 3.0f64);
203 }
204
205 #[test]
209 fn as_plain_does_not_panic_on_negative_scale() {
210 let decimal = Decimal::new(5.into(), -3);
211 let _ = decimal.as_plain();
215 }
216}