opensrv_clickhouse/types/
decimal.rs

1// Copyright 2021 Datafuse Labs.
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 at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16use std::fmt;
17
18static FACTORS10: &[i64] = &[
19    1,
20    10,
21    100,
22    1000,
23    10000,
24    100_000,
25    1_000_000,
26    10_000_000,
27    100_000_000,
28    1_000_000_000,
29    10_000_000_000,
30    100_000_000_000,
31    1_000_000_000_000,
32    10_000_000_000_000,
33    100_000_000_000_000,
34    1_000_000_000_000_000,
35    10_000_000_000_000_000,
36    100_000_000_000_000_000,
37    1_000_000_000_000_000_000,
38];
39
40pub trait Base {
41    fn scale(self, scale: i64) -> i64;
42}
43
44pub trait InternalResult {
45    fn get(underlying: i64) -> Self;
46}
47
48#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
49pub enum NoBits {
50    N32,
51    N64,
52}
53
54/// Provides arbitrary-precision floating point decimal.
55#[derive(Clone)]
56pub struct Decimal {
57    pub(crate) underlying: i64,
58    pub(crate) nobits: NoBits, // its domain is {32, 64}
59    pub(crate) precision: u8,
60    pub(crate) scale: u8,
61}
62
63impl Default for Decimal {
64    fn default() -> Self {
65        Decimal {
66            underlying: 0,
67            precision: 9,
68            scale: 4,
69            nobits: NoBits::N32,
70        }
71    }
72}
73
74macro_rules! base_for {
75    ( $( $t:ty: $cast:expr ),* ) => {
76        $(
77            impl Base for $t {
78                fn scale(self, scale: i64) -> i64 {
79                    $cast(self * (scale as $t)) as i64
80                }
81            }
82        )*
83    };
84}
85
86base_for! {
87    f32: std::convert::identity,
88    f64: std::convert::identity,
89    i8: i64::from,
90    i16: i64::from,
91    i32: i64::from,
92    i64: std::convert::identity,
93    u8: i64::from,
94    u16: i64::from,
95    u32: i64::from,
96    u64 : std::convert::identity
97}
98
99impl InternalResult for i32 {
100    #[inline(always)]
101    fn get(underlying: i64) -> Self {
102        underlying as Self
103    }
104}
105
106impl InternalResult for i64 {
107    #[inline(always)]
108    fn get(underlying: i64) -> Self {
109        underlying
110    }
111}
112
113impl NoBits {
114    pub(crate) fn from_precision(precision: u8) -> Option<NoBits> {
115        if precision <= 9 {
116            Some(NoBits::N32)
117        } else if precision <= 18 {
118            Some(NoBits::N64)
119        } else {
120            None
121        }
122    }
123}
124
125impl PartialEq for Decimal {
126    fn eq(&self, other: &Self) -> bool {
127        match self.scale.cmp(&other.scale) {
128            Ordering::Less => {
129                let delta = other.scale() - self.scale();
130                let underlying = self.underlying * FACTORS10[delta];
131                other.underlying == underlying
132            }
133            Ordering::Equal => self.underlying == other.underlying,
134            Ordering::Greater => {
135                let delta = self.scale() - other.scale();
136                let underlying = other.underlying * FACTORS10[delta];
137                self.underlying == underlying
138            }
139        }
140    }
141}
142
143pub fn decimal2str(decimal: &Decimal) -> String {
144    let mut r = format!("{}", decimal.underlying);
145    while r.len() < decimal.scale() {
146        r.insert(0, '0');
147    }
148    let pos = r.len() - decimal.scale();
149    r.insert(pos, '.');
150    if r.starts_with('.') {
151        r.insert(0, '0');
152    }
153    r
154}
155
156impl fmt::Display for Decimal {
157    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
158        write!(f, "{}", decimal2str(self))
159    }
160}
161
162impl fmt::Debug for Decimal {
163    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
164        write!(f, "{}", decimal2str(self))
165    }
166}
167
168impl From<Decimal> for f32 {
169    fn from(value: Decimal) -> Self {
170        value.underlying as f32 / FACTORS10[value.scale()] as f32
171    }
172}
173
174impl From<Decimal> for f64 {
175    fn from(value: Decimal) -> Self {
176        value.underlying as f64 / FACTORS10[value.scale()] as f64
177    }
178}
179
180impl Decimal {
181    /// Method of creating a Decimal.
182    pub fn new(underlying: i64, scale: u8) -> Decimal {
183        let precision = 18;
184        if scale > precision {
185            panic!("scale can't be greater than 18");
186        }
187
188        Decimal {
189            underlying,
190            precision,
191            scale,
192            nobits: NoBits::N64,
193        }
194    }
195
196    pub fn of<B: Base>(source: B, scale: u8) -> Decimal {
197        let precision = 18;
198        if scale > precision {
199            panic!("scale can't be greater than 18");
200        }
201
202        let underlying = source.scale(FACTORS10[scale as usize]);
203        if underlying > FACTORS10[precision as usize] {
204            panic!("{} > {}", underlying, FACTORS10[precision as usize]);
205        }
206
207        Decimal {
208            underlying,
209            precision,
210            scale,
211            nobits: NoBits::N64,
212        }
213    }
214
215    /*
216    /// Get the internal representation of decimal as [`i32`] or [`i64`].
217    ///
218    /// example:
219    /// ```rust
220    /// # use std::env;
221    /// # use clickhouse_rs::{Pool, types::Decimal, errors::Result};
222    /// # let mut rt = tokio::runtime::Runtime::new().unwrap();
223    /// # let ret: Result<()> = rt.block_on(async {
224    /// #     let database_url = env::var("DATABASE_URL")
225    /// #         .unwrap_or("tcp://localhost:9000?compression=lz4".into());
226    /// #     let pool = Pool::new(database_url);
227    ///       let mut c = pool.get_handle().await?;
228    ///       let block = c.query("SELECT toDecimal32(2, 4) AS x").fetch_all().await?;
229    ///
230    ///       let x: Decimal = block.get(0, "x")?;
231    ///       let actual: i32 = x.internal();
232    ///       assert_eq!(20000, actual);
233    /// #     Ok(())
234    /// # });
235    /// # ret.unwrap()
236    /// ```
237     */
238    #[inline(always)]
239    pub fn internal<I: InternalResult>(&self) -> I {
240        InternalResult::get(self.underlying)
241    }
242
243    /// Determines how many decimal digits fraction can have.
244    pub fn scale(&self) -> usize {
245        self.scale as usize
246    }
247
248    #[must_use]
249    pub fn set_scale(self, scale: u8) -> Self {
250        let underlying = match scale.cmp(&self.scale) {
251            Ordering::Less => {
252                let delta = self.scale() - scale as usize;
253                self.underlying / FACTORS10[delta]
254            }
255            Ordering::Equal => return self,
256            Ordering::Greater => {
257                let delta = scale as usize - self.scale();
258                self.underlying * FACTORS10[delta]
259            }
260        };
261
262        Decimal {
263            underlying,
264            precision: self.precision,
265            scale,
266            nobits: self.nobits,
267        }
268    }
269}