|
| 1 | +#[cfg(feature="bigdecimal")] |
| 2 | +mod bigdecimal { |
| 3 | + extern crate num_traits; |
| 4 | + extern crate num_bigint; |
| 5 | + extern crate num_integer; |
| 6 | + extern crate bigdecimal; |
| 7 | + |
| 8 | + use std::error::Error; |
| 9 | + use std::io::prelude::*; |
| 10 | + |
| 11 | + use pg::Pg; |
| 12 | + |
| 13 | + use self::num_traits::{Signed, Zero, ToPrimitive}; |
| 14 | + use self::num_bigint::{Sign, BigInt, BigUint, ToBigInt}; |
| 15 | + use self::num_integer::Integer; |
| 16 | + use self::bigdecimal::BigDecimal; |
| 17 | + |
| 18 | + use pg::data_types::PgNumeric; |
| 19 | + use types::{self, FromSql, ToSql, IsNull}; |
| 20 | + |
| 21 | + type Digits = Vec<i16>; |
| 22 | + |
| 23 | + fn bigdec_add_integer_part(digits: &mut Digits, absolute: &BigDecimal) -> i16 { |
| 24 | + let mut weight = 0; |
| 25 | + let ten_k = BigInt::from(10000); |
| 26 | + |
| 27 | + let mut integer_part = absolute.to_bigint().expect("Can always take integer part of BigDecimal"); |
| 28 | + |
| 29 | + while ten_k < integer_part { |
| 30 | + weight += 1; |
| 31 | + // digit is integer_part REM 10_000 |
| 32 | + let (div, digit) = integer_part.div_rem(&ten_k); |
| 33 | + digits.push(digit.to_u16().expect("digit < 10000, but cannot fit in i16") as i16); |
| 34 | + integer_part = div; |
| 35 | + } |
| 36 | + digits.push(integer_part.to_string().parse::<i16>().expect("digit < 10000, but cannot fit in i16")); |
| 37 | + |
| 38 | + digits.reverse(); |
| 39 | + |
| 40 | + weight |
| 41 | + } |
| 42 | + |
| 43 | + fn bigdec_add_decimal_part(digits: &mut Digits, absolute: &BigDecimal) -> u16 { |
| 44 | + use std::str::FromStr; |
| 45 | + |
| 46 | + let ten_k = BigDecimal::from_str("10000").expect("Could not parse into BigDecimal"); |
| 47 | + |
| 48 | + let decimal_part = absolute; |
| 49 | + let mut decimal_part = decimal_part - absolute.with_scale(0); |
| 50 | + // scale is the amount of digits to print. to_string() includes a "0.", |
| 51 | + // that's why the -2 is there. |
| 52 | + let scale = if decimal_part == Zero::zero() { |
| 53 | + 0 |
| 54 | + } else { |
| 55 | + decimal_part.to_string().len() as u16 - 2 |
| 56 | + }; |
| 57 | + |
| 58 | + while decimal_part != BigDecimal::zero() { |
| 59 | + decimal_part *= &ten_k; |
| 60 | + let digit = decimal_part.to_bigint().expect("Can always take integer part of BigDecimal"); |
| 61 | + |
| 62 | + // This can be simplified when github.com/akubera/bigdecimal-rs/issues/13 gets |
| 63 | + // solved; decimal_part -= &digit; should suffice by then. |
| 64 | + decimal_part -= BigDecimal::new(digit.clone(), 0); |
| 65 | + digits.push(digit.to_u16().expect("digit < 10000, but cannot fit in i16") as i16); |
| 66 | + } |
| 67 | + |
| 68 | + scale |
| 69 | + } |
| 70 | + |
| 71 | + impl ToSql<types::Numeric, Pg> for BigDecimal { |
| 72 | + fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<Error + Send + Sync>> { |
| 73 | + // The encoding of the BigDecimal type for PostgreSQL is a bit complicated: |
| 74 | + // PostgreSQL expects the data in base-10000 (so two bytes per 10k), |
| 75 | + // and the decimal point should lie on a boundary (as per definition of "base-10000"). |
| 76 | + |
| 77 | + // BigDecimal, internally, holds an int vector (base-256, one byte per byte), |
| 78 | + // and a base (u64, base-10) shift. |
| 79 | + |
| 80 | + // Therefore, we split up the encoding in three parts: |
| 81 | + // the sign, the (integer) part before the decimal, and the part after the decimal. |
| 82 | + |
| 83 | + let absolute = self.abs(); |
| 84 | + let mut digits = vec![]; |
| 85 | + |
| 86 | + // Encode the integer part |
| 87 | + let weight = bigdec_add_integer_part(&mut digits, &absolute); |
| 88 | + |
| 89 | + // Encode the decimal part |
| 90 | + let scale = bigdec_add_decimal_part(&mut digits, &absolute); |
| 91 | + |
| 92 | + let numeric = match self.sign() { |
| 93 | + Sign::Plus => PgNumeric::Positive { |
| 94 | + digits, scale, weight |
| 95 | + }, |
| 96 | + Sign::Minus => PgNumeric::Negative { |
| 97 | + digits, scale, weight |
| 98 | + }, |
| 99 | + Sign::NoSign => PgNumeric::Positive { |
| 100 | + digits: vec![0], |
| 101 | + scale: 0, |
| 102 | + weight: 0, |
| 103 | + }, |
| 104 | + }; |
| 105 | + ToSql::<types::Numeric, Pg>::to_sql(&numeric, out) |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + impl FromSql<types::Numeric, Pg> for BigDecimal { |
| 110 | + fn from_sql(numeric: Option<&[u8]>) -> Result<Self, Box<Error+Send+Sync>> { |
| 111 | + let (sign, weight, _, digits) = match PgNumeric::from_sql(numeric)? { |
| 112 | + PgNumeric::Positive { weight, scale, digits } => (Sign::Plus, weight, scale, digits), |
| 113 | + PgNumeric::Negative { weight, scale, digits } => (Sign::Minus, weight, scale, digits), |
| 114 | + PgNumeric::NaN => return Err(Box::from("NaN is not (yet) supported in BigDecimal")), |
| 115 | + }; |
| 116 | + let mut result = BigUint::default(); |
| 117 | + let count = digits.len() as i64; |
| 118 | + for digit in digits { |
| 119 | + result = result * BigUint::from(10_000u64); |
| 120 | + result = result + BigUint::from(digit as u64); |
| 121 | + } |
| 122 | + // First digit got factor 10_000^(digits.len() - 1), but should get 10_000^weight |
| 123 | + let correction_exp = 4 * ( (weight as i64) - count + 1); |
| 124 | + // FIXME: `scale` allows to drop some insignificant figures, which is currently unimplemented. |
| 125 | + // This means that e.g. PostgreSQL 0.01 will be interpreted as 0.0100 |
| 126 | + let result = BigDecimal::new(BigInt::from_biguint(sign, result), -correction_exp); |
| 127 | + Ok(result) |
| 128 | + } |
| 129 | + } |
| 130 | +} |
0 commit comments