dec/
conv.rs

1// Copyright Materialize, Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16/// Converts from some arbitrary signed integer `$n` whose size is a multiple of
17/// 32 into a decimal of type `$t`.
18///
19/// `$cx` is a `Context::<$t>` used to generate a value of `$t`. It must outlive
20/// the macro call to, e.g., allow checking the context's status.
21macro_rules! from_signed_int {
22    ($t:ty, $cx:expr, $n:expr) => {
23        __from_int!($t, i32, $cx, $n)
24    };
25}
26
27/// Like `from_signed_int!` but for unsigned integers.
28macro_rules! from_unsigned_int {
29    ($t:ty, $cx:expr, $n:expr) => {
30        __from_int!($t, u32, $cx, $n)
31    };
32}
33
34macro_rules! __from_int {
35    ($t:ty, $l:ty, $cx:expr, $n:expr) => {{
36        let n = $n.to_be_bytes();
37        assert!(
38            n.len() % 4 == 0 && n.len() >= 4,
39            "from_int requires size of integer to be a multiple of 32"
40        );
41
42        // Process `$n` in 32-bit chunks. Only the first chunk has to be sign
43        // aware. Each turn of the loop computes `d = d * 2^32 + n`, where `n`
44        // is the next 32-bit chunk.
45        let mut d = <$t>::from(<$l>::from_be_bytes(n[..4].try_into().unwrap()));
46        for i in (4..n.len()).step_by(4) {
47            d = $cx.mul(d, <$t>::TWO_POW_32);
48            let n = <$t>::from(u32::from_be_bytes(n[i..i + 4].try_into().unwrap()));
49            d = $cx.add(d, n);
50        }
51
52        d
53    }};
54}
55
56macro_rules! decimal_from_signed_int {
57    ($cx:expr, $n:expr) => {
58        __decimal_from_int!(i32, $cx, $n)
59    };
60}
61
62macro_rules! decimal_from_unsigned_int {
63    ($cx:expr, $n:expr) => {
64        __decimal_from_int!(u32, $cx, $n)
65    };
66}
67
68// Equivalent to `__from_int`, but with `Decimal`'s API.
69macro_rules! __decimal_from_int {
70    ($l:ty, $cx:expr, $n:expr) => {{
71        let n = $n.to_be_bytes();
72        assert!(
73            n.len() % 4 == 0 && n.len() >= 4,
74            "from_int requires size of integer to be a multiple of 32"
75        );
76        let two_pow_32 = Decimal::<N>::two_pow_32();
77
78        let mut d = Decimal::<N>::from(<$l>::from_be_bytes(n[..4].try_into().unwrap()));
79        for i in (4..n.len()).step_by(4) {
80            $cx.mul(&mut d, &two_pow_32);
81            let n = Decimal::<N>::from(u32::from_be_bytes(n[i..i + 4].try_into().unwrap()));
82            $cx.add(&mut d, &n);
83        }
84
85        d
86    }};
87}
88
89/// Looks up character representation of a densely packed digit.
90macro_rules! dpd2char {
91    ($dpd:expr, $digits:expr, $digits_idx:expr) => {{
92        let mut u = [0u8; 4];
93        let bin_idx = (unsafe { decnumber_sys::DPD2BIN[$dpd] } as usize) << 2;
94        u.copy_from_slice(unsafe { &decnumber_sys::BIN2CHAR[bin_idx..bin_idx + 4] });
95        if $digits_idx > 0 {
96            $digits[$digits_idx..$digits_idx + 3].copy_from_slice(&u[1..4]);
97            $digits_idx += 3;
98        } else if u[0] > 0 {
99            // skip leading zeroes; left align first value
100            let d = (4 - u[0]) as usize;
101            $digits[$digits_idx..$digits_idx + u[0] as usize].copy_from_slice(&u[d..4]);
102            $digits_idx += u[0] as usize;
103        }
104    }};
105}
106
107/// Produces a string-ified version of a `Vec<char>` derived from a decimal.
108macro_rules! stringify_digits {
109    ($s:expr, $digits:expr, $digits_idx:expr) => {{
110        if $digits_idx == 0 {
111            $digits[0] = b'0';
112            $digits_idx = 1;
113        }
114
115        let e = $s.exponent();
116        if e >= 0 {
117            let mut s = String::with_capacity($digits_idx + e as usize + 1);
118            if $s.is_negative() {
119                s.push('-');
120            }
121            s.push_str(unsafe { std::str::from_utf8_unchecked(&$digits[..$digits_idx]) });
122            if $digits[0] != b'0' {
123                for _ in 0..e {
124                    s.push('0');
125                }
126            }
127            s
128        } else if $digits_idx as i32 > -e {
129            let mut s = String::with_capacity($digits_idx + 2);
130            if $s.is_negative() {
131                s.push('-');
132            }
133            let e = ($digits_idx as i32 + e) as usize;
134            for d in &$digits[..e] {
135                s.push(char::from(*d));
136            }
137            s.push('.');
138            for d in &$digits[e..$digits_idx] {
139                s.push(char::from(*d));
140            }
141            s
142        } else {
143            let d = usize::try_from(-e).unwrap() - $digits_idx;
144            let mut s = String::with_capacity($digits_idx + d + 3);
145            if $s.is_negative() {
146                s.push('-');
147            }
148            // All digits after the decimal point.
149            s.push_str("0.");
150            for _ in 0..d {
151                s.push('0');
152            }
153            for d in &$digits[..$digits_idx] {
154                s.push(char::from(*d));
155            }
156            s
157        }
158    }};
159}