1use crate::arguments::PgArgumentBuffer;
2use crate::types::decode::Decode;
3use crate::types::encode::{Encode, IsNull};
4use crate::types::numeric::{PgNumeric, PgNumericSign};
5use crate::value::{PgValue, PgValueFormat};
6use bigdecimal::BigDecimal;
7use num_bigint::{BigInt, Sign};
8use rbdc::Error;
9use std::cmp;
10use std::num::TryFromIntError;
11
12impl TryFrom<PgNumeric> for BigDecimal {
13 type Error = Error;
14
15 fn try_from(numeric: PgNumeric) -> Result<Self, Error> {
16 let (digits, sign, weight) = match numeric {
17 PgNumeric::Number {
18 digits,
19 sign,
20 weight,
21 ..
22 } => (digits, sign, weight),
23
24 PgNumeric::NotANumber => {
25 return Err("BigDecimal does not support NaN values".into());
26 }
27 };
28
29 if digits.is_empty() {
30 return Ok(0u64.into());
32 }
33
34 let sign = match sign {
35 PgNumericSign::Positive => Sign::Plus,
36 PgNumericSign::Negative => Sign::Minus,
37 };
38
39 let scale = (digits.len() as i64 - weight as i64 - 1) * 4;
41
42 let mut cents = Vec::with_capacity(digits.len() * 2);
44 for digit in &digits {
45 cents.push((digit / 100) as u8);
46 cents.push((digit % 100) as u8);
47 }
48
49 let bigint = BigInt::from_radix_be(sign, ¢s, 100)
50 .ok_or("PgNumeric contained an out-of-range digit")?;
51
52 Ok(BigDecimal::new(bigint, scale))
53 }
54}
55
56impl TryFrom<&'_ BigDecimal> for PgNumeric {
57 type Error = Error;
58
59 fn try_from(decimal: &BigDecimal) -> Result<Self, Error> {
60 let base_10_to_10000 = |chunk: &[u8]| chunk.iter().fold(0i16, |a, &d| a * 10 + d as i16);
61
62 let (integer, exp) = decimal.as_bigint_and_exponent();
64
65 let (sign, base_10) = integer.to_radix_be(10);
68
69 let weight_10 = base_10.len() as i64 - exp;
72
73 let scale: i16 = cmp::max(0, exp)
77 .try_into()
78 .map_err(|e: TryFromIntError| Error::from(e.to_string()))?;
79
80 let weight: i16 = if weight_10 <= 0 {
82 weight_10 / 4 - 1
83 } else {
84 (weight_10 - 1) / 4
86 }
87 .try_into()
88 .map_err(|e: TryFromIntError| Error::from(e.to_string()))?;
89
90 let digits_len = if base_10.len() % 4 != 0 {
91 base_10.len() / 4 + 1
92 } else {
93 base_10.len() / 4
94 };
95
96 let offset = weight_10.rem_euclid(4) as usize;
97
98 let mut digits = Vec::with_capacity(digits_len);
99
100 if let Some(first) = base_10.get(..offset) {
101 if !first.is_empty() {
102 digits.push(base_10_to_10000(first));
103 }
104 } else if offset != 0 {
105 digits.push(base_10_to_10000(&base_10) * 10i16.pow((offset - base_10.len()) as u32));
106 }
107
108 if let Some(rest) = base_10.get(offset..) {
109 digits.extend(
110 rest.chunks(4)
111 .map(|chunk| base_10_to_10000(chunk) * 10i16.pow(4 - chunk.len() as u32)),
112 );
113 }
114
115 while let Some(&0) = digits.last() {
116 digits.pop();
117 }
118
119 Ok(PgNumeric::Number {
120 sign: match sign {
121 Sign::Plus | Sign::NoSign => PgNumericSign::Positive,
122 Sign::Minus => PgNumericSign::Negative,
123 },
124 scale,
125 weight,
126 digits,
127 })
128 }
129}
130
131impl Encode for BigDecimal {
134 fn encode(self, buf: &mut PgArgumentBuffer) -> Result<IsNull, Error> {
135 PgNumeric::try_from(&self)
136 .expect("BigDecimal magnitude too great for Postgres NUMERIC type")
137 .encode(buf);
138 Ok(IsNull::No)
139 }
140}
141
142impl Decode for BigDecimal {
143 fn decode(value: PgValue) -> Result<Self, Error> {
144 match value.format() {
145 PgValueFormat::Binary => PgNumeric::decode(value.as_bytes()?)?.try_into(),
146 PgValueFormat::Text => Ok(value
147 .as_str()?
148 .parse::<BigDecimal>()
149 .map_err(|e| Error::from(e.to_string()))?),
150 }
151 }
152}
153
154#[cfg(test)]
155mod bigdecimal_to_pgnumeric {
156 use super::{BigDecimal, PgNumeric, PgNumericSign};
157
158 #[test]
159 fn zero() {
160 let zero: BigDecimal = "0".parse().unwrap();
161
162 assert_eq!(
163 PgNumeric::try_from(&zero).unwrap(),
164 PgNumeric::Number {
165 sign: PgNumericSign::Positive,
166 scale: 0,
167 weight: 0,
168 digits: vec![]
169 }
170 );
171 }
172
173 #[test]
174 fn one() {
175 let one: BigDecimal = "1".parse().unwrap();
176 assert_eq!(
177 PgNumeric::try_from(&one).unwrap(),
178 PgNumeric::Number {
179 sign: PgNumericSign::Positive,
180 scale: 0,
181 weight: 0,
182 digits: vec![1]
183 }
184 );
185 }
186
187 #[test]
188 fn ten() {
189 let ten: BigDecimal = "10".parse().unwrap();
190 assert_eq!(
191 PgNumeric::try_from(&ten).unwrap(),
192 PgNumeric::Number {
193 sign: PgNumericSign::Positive,
194 scale: 0,
195 weight: 0,
196 digits: vec![10]
197 }
198 );
199 }
200
201 #[test]
202 fn one_hundred() {
203 let one_hundred: BigDecimal = "100".parse().unwrap();
204 assert_eq!(
205 PgNumeric::try_from(&one_hundred).unwrap(),
206 PgNumeric::Number {
207 sign: PgNumericSign::Positive,
208 scale: 0,
209 weight: 0,
210 digits: vec![100]
211 }
212 );
213 }
214
215 #[test]
216 fn ten_thousand() {
217 let ten_thousand: BigDecimal = "10000".parse().unwrap();
219 assert_eq!(
220 PgNumeric::try_from(&ten_thousand).unwrap(),
221 PgNumeric::Number {
222 sign: PgNumericSign::Positive,
223 scale: 0,
224 weight: 1,
225 digits: vec![1]
226 }
227 );
228 }
229
230 #[test]
231 fn two_digits() {
232 let two_digits: BigDecimal = "12345".parse().unwrap();
233 assert_eq!(
234 PgNumeric::try_from(&two_digits).unwrap(),
235 PgNumeric::Number {
236 sign: PgNumericSign::Positive,
237 scale: 0,
238 weight: 1,
239 digits: vec![1, 2345]
240 }
241 );
242 }
243
244 #[test]
245 fn one_tenth() {
246 let one_tenth: BigDecimal = "0.1".parse().unwrap();
247 assert_eq!(
248 PgNumeric::try_from(&one_tenth).unwrap(),
249 PgNumeric::Number {
250 sign: PgNumericSign::Positive,
251 scale: 1,
252 weight: -1,
253 digits: vec![1000]
254 }
255 );
256 }
257
258 #[test]
259 fn one_hundredth() {
260 let one_hundredth: BigDecimal = "0.01".parse().unwrap();
261 assert_eq!(
262 PgNumeric::try_from(&one_hundredth).unwrap(),
263 PgNumeric::Number {
264 sign: PgNumericSign::Positive,
265 scale: 2,
266 weight: -1,
267 digits: vec![100]
268 }
269 );
270 }
271
272 #[test]
273 fn twelve_thousandths() {
274 let twelve_thousandths: BigDecimal = "0.012".parse().unwrap();
275 assert_eq!(
276 PgNumeric::try_from(&twelve_thousandths).unwrap(),
277 PgNumeric::Number {
278 sign: PgNumericSign::Positive,
279 scale: 3,
280 weight: -1,
281 digits: vec![120]
282 }
283 );
284 }
285
286 #[test]
287 fn decimal_1() {
288 let decimal: BigDecimal = "1.2345".parse().unwrap();
289 assert_eq!(
290 PgNumeric::try_from(&decimal).unwrap(),
291 PgNumeric::Number {
292 sign: PgNumericSign::Positive,
293 scale: 4,
294 weight: 0,
295 digits: vec![1, 2345]
296 }
297 );
298 }
299
300 #[test]
301 fn decimal_2() {
302 let decimal: BigDecimal = "0.12345".parse().unwrap();
303 assert_eq!(
304 PgNumeric::try_from(&decimal).unwrap(),
305 PgNumeric::Number {
306 sign: PgNumericSign::Positive,
307 scale: 5,
308 weight: -1,
309 digits: vec![1234, 5000]
310 }
311 );
312 }
313
314 #[test]
315 fn decimal_3() {
316 let decimal: BigDecimal = "0.01234".parse().unwrap();
317 assert_eq!(
318 PgNumeric::try_from(&decimal).unwrap(),
319 PgNumeric::Number {
320 sign: PgNumericSign::Positive,
321 scale: 5,
322 weight: -1,
323 digits: vec![0123, 4000]
324 }
325 );
326 }
327
328 #[test]
329 fn decimal_4() {
330 let decimal: BigDecimal = "12345.67890".parse().unwrap();
331 assert_eq!(
332 PgNumeric::try_from(&decimal).unwrap(),
333 PgNumeric::Number {
334 sign: PgNumericSign::Positive,
335 scale: 5,
336 weight: 1,
337 digits: vec![1, 2345, 6789]
338 }
339 );
340 }
341
342 #[test]
343 fn one_digit_decimal() {
344 let one_digit_decimal: BigDecimal = "0.00001234".parse().unwrap();
345 assert_eq!(
346 PgNumeric::try_from(&one_digit_decimal).unwrap(),
347 PgNumeric::Number {
348 sign: PgNumericSign::Positive,
349 scale: 8,
350 weight: -2,
351 digits: vec![1234]
352 }
353 );
354 }
355
356 #[test]
357 fn issue_423_four_digit() {
358 let four_digit: BigDecimal = "1234".parse().unwrap();
359 assert_eq!(
360 PgNumeric::try_from(&four_digit).unwrap(),
361 PgNumeric::Number {
362 sign: PgNumericSign::Positive,
363 scale: 0,
364 weight: 0,
365 digits: vec![1234]
366 }
367 );
368 }
369
370 #[test]
371 fn issue_423_negative_four_digit() {
372 let negative_four_digit: BigDecimal = "-1234".parse().unwrap();
373 assert_eq!(
374 PgNumeric::try_from(&negative_four_digit).unwrap(),
375 PgNumeric::Number {
376 sign: PgNumericSign::Negative,
377 scale: 0,
378 weight: 0,
379 digits: vec![1234]
380 }
381 );
382 }
383
384 #[test]
385 fn issue_423_eight_digit() {
386 let eight_digit: BigDecimal = "12345678".parse().unwrap();
387 assert_eq!(
388 PgNumeric::try_from(&eight_digit).unwrap(),
389 PgNumeric::Number {
390 sign: PgNumericSign::Positive,
391 scale: 0,
392 weight: 1,
393 digits: vec![1234, 5678]
394 }
395 );
396 }
397
398 #[test]
399 fn issue_423_negative_eight_digit() {
400 let negative_eight_digit: BigDecimal = "-12345678".parse().unwrap();
401 assert_eq!(
402 PgNumeric::try_from(&negative_eight_digit).unwrap(),
403 PgNumeric::Number {
404 sign: PgNumericSign::Negative,
405 scale: 0,
406 weight: 1,
407 digits: vec![1234, 5678]
408 }
409 );
410 }
411}