@@ -5,91 +5,62 @@ mod bigdecimal {
55 extern crate num_integer;
66 extern crate bigdecimal;
77
8+ use self :: bigdecimal:: BigDecimal ;
9+ use self :: num_bigint:: { Sign , BigInt , BigUint } ;
10+ use self :: num_integer:: Integer ;
11+ use self :: num_traits:: { Signed , Zero , ToPrimitive } ;
812 use std:: error:: Error ;
913 use std:: io:: prelude:: * ;
1014
1115 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-
1816 use pg:: data_types:: PgNumeric ;
1917 use types:: { self , FromSql , ToSql , ToSqlOutput , IsNull } ;
2018
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 ) ;
19+ /// Iterator over the digits of a big uint in base 10k.
20+ /// The digits will be returned in little endian order.
21+ struct ToBase10000 ( Option < BigUint > ) ;
22+
23+ impl Iterator for ToBase10000 {
24+ type Item = i16 ;
25+
26+ fn next ( & mut self ) -> Option < Self :: Item > {
27+ self . 0 . take ( ) . map ( |v| {
28+ let ( div, rem) = v. div_rem ( & BigUint :: from ( 10000u16 ) ) ;
29+ if !div. is_zero ( ) {
30+ self . 0 = Some ( div) ;
31+ }
32+ rem. to_i16 ( ) . expect ( "10000 always fits in an i16" )
33+ } )
6634 }
67-
68- scale
6935 }
7036
71- impl ToSql < types:: Numeric , Pg > for BigDecimal {
72- fn to_sql < W : Write > ( & self , out : & mut ToSqlOutput < W , Pg > ) -> 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) ;
37+ impl < ' a > From < & ' a BigDecimal > for PgNumeric {
38+ fn from ( decimal : & ' a BigDecimal ) -> Self {
39+ let ( mut integer, scale) = decimal. as_bigint_and_exponent ( ) ;
40+ let scale = scale as u16 ;
41+ integer = integer. abs ( ) ;
8842
89- // Encode the decimal part
90- let scale = bigdec_add_decimal_part ( & mut digits, & absolute) ;
91-
92- let numeric = match self . sign ( ) {
43+ // Ensure that the decimal will always lie on a digit boundary
44+ for _ in 0 ..( 4 - scale % 4 ) {
45+ integer = integer * 10 ;
46+ }
47+ let integer = integer. to_biguint ( ) . expect ( "integer is always positive" ) ;
48+
49+ let mut digits = ToBase10000 ( Some ( integer) ) . collect :: < Vec < _ > > ( ) ;
50+ digits. reverse ( ) ;
51+ let digits_after_decimal = scale as u16 / 4 + 1 ;
52+ let weight = digits. len ( ) as i16 - digits_after_decimal as i16 - 1 ;
53+ let index_of_decimal = ( weight + 1 ) as usize ;
54+
55+ let unneccessary_zeroes = digits[ index_of_decimal..]
56+ . iter ( )
57+ . rev ( )
58+ . take_while ( |i| i. is_zero ( ) )
59+ . count ( ) ;
60+ let relevant_digits = digits. len ( ) - unneccessary_zeroes;
61+ digits. truncate ( relevent_digits) ;
62+
63+ match decimal. sign ( ) {
9364 Sign :: Plus => PgNumeric :: Positive {
9465 digits, scale, weight
9566 } ,
@@ -101,7 +72,19 @@ mod bigdecimal {
10172 scale : 0 ,
10273 weight : 0 ,
10374 } ,
104- } ;
75+ }
76+ }
77+ }
78+
79+ impl From < BigDecimal > for PgNumeric {
80+ fn from ( bigdecimal : BigDecimal ) -> Self {
81+ ( & bigdecimal) . into ( )
82+ }
83+ }
84+
85+ impl ToSql < types:: Numeric , Pg > for BigDecimal {
86+ fn to_sql < W : Write > ( & self , out : & mut ToSqlOutput < W , Pg > ) -> Result < IsNull , Box < Error + Send + Sync > > {
87+ let numeric = PgNumeric :: from ( self ) ;
10588 ToSql :: < types:: Numeric , Pg > :: to_sql ( & numeric, out)
10689 }
10790 }
@@ -127,4 +110,83 @@ mod bigdecimal {
127110 Ok ( result)
128111 }
129112 }
113+
114+ #[ cfg( test) ]
115+ mod tests {
116+ use super :: * ;
117+ use std:: str:: FromStr ;
118+
119+ #[ test]
120+ fn bigdecimal_to_pgnumeric_converts_digits_to_base_10000 ( ) {
121+ let decimal = BigDecimal :: from_str ( "1" ) . unwrap ( ) ;
122+ let expected = PgNumeric :: Positive { weight : 0 , scale : 0 , digits : vec ! [ 1 ] } ;
123+ assert_eq ! ( expected, decimal. into( ) ) ;
124+
125+ let decimal = BigDecimal :: from_str ( "10" ) . unwrap ( ) ;
126+ let expected = PgNumeric :: Positive { weight : 0 , scale : 0 , digits : vec ! [ 10 ] } ;
127+ assert_eq ! ( expected, decimal. into( ) ) ;
128+
129+ let decimal = BigDecimal :: from_str ( "10000" ) . unwrap ( ) ;
130+ let expected = PgNumeric :: Positive { weight : 1 , scale : 0 , digits : vec ! [ 1 , 0 ] } ;
131+ assert_eq ! ( expected, decimal. into( ) ) ;
132+
133+ let decimal = BigDecimal :: from_str ( "10001" ) . unwrap ( ) ;
134+ let expected = PgNumeric :: Positive { weight : 1 , scale : 0 , digits : vec ! [ 1 , 1 ] } ;
135+ assert_eq ! ( expected, decimal. into( ) ) ;
136+
137+ let decimal = BigDecimal :: from_str ( "100000000" ) . unwrap ( ) ;
138+ let expected = PgNumeric :: Positive { weight : 2 , scale : 0 , digits : vec ! [ 1 , 0 , 0 ] } ;
139+ assert_eq ! ( expected, decimal. into( ) ) ;
140+ }
141+
142+ #[ test]
143+ fn bigdecimal_to_pg_numeric_properly_adjusts_scale ( ) {
144+ let decimal = BigDecimal :: from_str ( "1" ) . unwrap ( ) ;
145+ let expected = PgNumeric :: Positive { weight : 0 , scale : 0 , digits : vec ! [ 1 ] } ;
146+ assert_eq ! ( expected, decimal. into( ) ) ;
147+
148+ let decimal = BigDecimal :: from_str ( "1.0" ) . unwrap ( ) ;
149+ let expected = PgNumeric :: Positive { weight : 0 , scale : 1 , digits : vec ! [ 1 ] } ;
150+ assert_eq ! ( expected, decimal. into( ) ) ;
151+
152+ let decimal = BigDecimal :: from_str ( "1.1" ) . unwrap ( ) ;
153+ let expected = PgNumeric :: Positive { weight : 0 , scale : 1 , digits : vec ! [ 1 , 1000 ] } ;
154+ assert_eq ! ( expected, decimal. into( ) ) ;
155+
156+ let decimal = BigDecimal :: from_str ( "1.10" ) . unwrap ( ) ;
157+ let expected = PgNumeric :: Positive { weight : 0 , scale : 2 , digits : vec ! [ 1 , 1000 ] } ;
158+ assert_eq ! ( expected, decimal. into( ) ) ;
159+
160+ let decimal = BigDecimal :: from_str ( "100000000.0001" ) . unwrap ( ) ;
161+ let expected = PgNumeric :: Positive {
162+ weight : 2 ,
163+ scale : 4 ,
164+ digits : vec ! [ 1 , 0 , 0 , 1 ] ,
165+ } ;
166+ assert_eq ! ( expected, decimal. into( ) ) ;
167+
168+ let decimal = BigDecimal :: from_str ( "0.1" ) . unwrap ( ) ;
169+ let expected = PgNumeric :: Positive { weight : -1 , scale : 1 , digits : vec ! [ 1000 ] } ;
170+ assert_eq ! ( expected, decimal. into( ) ) ;
171+ }
172+
173+ #[ test]
174+ fn bigdecimal_to_pg_numeric_retains_sign ( ) {
175+ let decimal = BigDecimal :: from_str ( "123.456" ) . unwrap ( ) ;
176+ let expected = PgNumeric :: Positive {
177+ weight : 0 ,
178+ scale : 3 ,
179+ digits : vec ! [ 123 , 4560 ] ,
180+ } ;
181+ assert_eq ! ( expected, decimal. into( ) ) ;
182+
183+ let decimal = BigDecimal :: from_str ( "-123.456" ) . unwrap ( ) ;
184+ let expected = PgNumeric :: Negative {
185+ weight : 0 ,
186+ scale : 3 ,
187+ digits : vec ! [ 123 , 4560 ] ,
188+ } ;
189+ assert_eq ! ( expected, decimal. into( ) ) ;
190+ }
191+ }
130192}
0 commit comments