clickhouse_arrow/native/values/
fixed_point.rs1#![expect(clippy::cast_possible_truncation)]
2#![expect(clippy::cast_precision_loss)]
3#![expect(clippy::cast_sign_loss)]
4#![cfg_attr(feature = "rust_decimal", expect(clippy::cast_possible_wrap))]
5use crate::{FromSql, Result, ToSql, Type, Value, i256, unexpected_type};
6
7#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct FixedPoint32<const SCALE: u64>(pub i32);
11
12impl<const SCALE: u64> FixedPoint32<SCALE> {
13 pub const fn modulus(&self) -> i32 { 10i32.pow(SCALE as u32) }
14
15 pub fn integer(&self) -> i32 { self.0 / 10i32.pow(SCALE as u32) }
16
17 pub fn fraction(&self) -> i32 { self.0 % 10i32.pow(SCALE as u32) }
18}
19
20impl<const SCALE: u64> ToSql for FixedPoint32<SCALE> {
21 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
22 Ok(Value::Decimal32(SCALE as usize, self.0))
23 }
24}
25
26impl<const SCALE: u64> FromSql for FixedPoint32<SCALE> {
27 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
28 if !matches!(type_, Type::Decimal32(x) if *x == SCALE as usize) {
29 return Err(unexpected_type(type_));
30 }
31 match value {
32 Value::Decimal32(_, x) => Ok(Self(x)),
33 _ => unimplemented!(),
34 }
35 }
36}
37
38impl<const SCALE: u64> From<FixedPoint32<SCALE>> for f64 {
39 fn from(fp: FixedPoint32<SCALE>) -> Self {
40 f64::from(fp.integer()) + (f64::from(fp.fraction()) / f64::from(fp.modulus()))
41 }
42}
43
44#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct FixedPoint64<const SCALE: u64>(pub i64);
48
49impl<const SCALE: u64> ToSql for FixedPoint64<SCALE> {
50 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
51 Ok(Value::Decimal64(SCALE as usize, self.0))
52 }
53}
54
55impl<const SCALE: u64> FromSql for FixedPoint64<SCALE> {
56 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
57 if !matches!(type_, Type::Decimal64(x) if *x == SCALE as usize) {
58 return Err(unexpected_type(type_));
59 }
60 match value {
61 Value::Decimal64(_, x) => Ok(Self(x)),
62 _ => unimplemented!(),
63 }
64 }
65}
66
67impl<const SCALE: u64> FixedPoint64<SCALE> {
68 pub const fn modulus(&self) -> i64 { 10i64.pow(SCALE as u32) }
69
70 pub fn integer(&self) -> i64 { self.0 / 10i64.pow(SCALE as u32) }
71
72 pub fn fraction(&self) -> i64 { self.0 % 10i64.pow(SCALE as u32) }
73}
74
75impl<const SCALE: u64> From<FixedPoint64<SCALE>> for f64 {
76 fn from(fp: FixedPoint64<SCALE>) -> Self {
77 fp.integer() as f64 + (fp.fraction() as f64 / fp.modulus() as f64)
78 }
79}
80
81#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84pub struct FixedPoint128<const SCALE: u64>(pub i128);
85
86impl<const SCALE: u64> ToSql for FixedPoint128<SCALE> {
87 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
88 Ok(Value::Decimal128(SCALE as usize, self.0))
89 }
90}
91
92impl<const SCALE: u64> FromSql for FixedPoint128<SCALE> {
93 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
94 if !matches!(type_, Type::Decimal128(x) if *x == SCALE as usize) {
95 return Err(unexpected_type(type_));
96 }
97 match value {
98 Value::Decimal128(_, x) => Ok(Self(x)),
99 _ => unimplemented!(),
100 }
101 }
102}
103
104impl<const SCALE: u64> FixedPoint128<SCALE> {
105 pub const fn modulus(&self) -> i128 { 10i128.pow(SCALE as u32) }
106
107 pub fn integer(&self) -> i128 { self.0 / 10i128.pow(SCALE as u32) }
108
109 pub fn fraction(&self) -> i128 { self.0 % 10i128.pow(SCALE as u32) }
110}
111
112impl<const SCALE: u64> From<FixedPoint128<SCALE>> for f64 {
113 fn from(fp: FixedPoint128<SCALE>) -> Self {
114 fp.integer() as f64 + (fp.fraction() as f64 / fp.modulus() as f64)
115 }
116}
117
118#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct FixedPoint256<const SCALE: u64>(pub i256);
122
123impl<const SCALE: u64> FixedPoint256<SCALE> {
124 pub const MAX: Self = FixedPoint256(Self::max_i256());
126 pub const MIN: Self = FixedPoint256(Self::min_i256());
128
129 const fn max_i256() -> i256 {
130 let mut bytes = [0xFF; 32];
133 bytes[0] = 0x7F; i256(bytes)
135 }
136
137 const fn min_i256() -> i256 {
138 let mut bytes = [0; 32];
141 bytes[0] = 0x80; i256(bytes)
143 }
144
145 pub fn from_raw(value: i128) -> Self { FixedPoint256(i256::from(value)) }
148
149 pub fn from_parts(value: i128, exponent: i32) -> Self {
152 let effective_scale = SCALE as i32 - exponent;
153
154 if effective_scale > 38 {
155 let base = i256::from(value);
157 let mut result = base;
158
159 for _ in 0..effective_scale {
161 result = result * i256::from(10i128);
162 }
163
164 FixedPoint256(result)
165 } else if effective_scale >= 0 {
166 let scaled_value = value * 10i128.pow(effective_scale as u32);
168 FixedPoint256(i256::from(scaled_value))
169 } else {
170 let scaled_value = value / 10i128.pow((-effective_scale) as u32);
172 FixedPoint256(i256::from(scaled_value))
173 }
174 }
175
176 #[cfg(feature = "rust_decimal")]
178 pub fn from_decimal(decimal: rust_decimal::Decimal) -> Self {
179 let scale = decimal.scale();
180 let mantissa = decimal.mantissa();
181
182 Self::from_parts(mantissa, scale as i32)
183 }
184
185 #[cfg(feature = "rust_decimal")]
191 pub fn to_decimal(&self) -> Result<rust_decimal::Decimal, rust_decimal::Error> {
192 let (high, low) = self.0.into();
194
195 if high != 0 && high != u128::MAX {
196 return Err(rust_decimal::Error::ExceedsMaximumPossibleValue);
198 }
199
200 let raw_value = if self.is_negative() {
201 let mut high_bits = !high;
203 let low_bits = !low;
204
205 let low_plus_one = low_bits.wrapping_add(1);
206 if low_plus_one == 0 {
207 high_bits = high_bits.wrapping_add(1);
208 }
209
210 if high_bits != 0 {
211 return Err(rust_decimal::Error::ExceedsMaximumPossibleValue);
213 }
214
215 -(low_plus_one as i128)
216 } else {
217 low as i128
219 };
220
221 rust_decimal::Decimal::try_from_i128_with_scale(raw_value, SCALE as u32)
223 }
224
225 pub fn is_negative(&self) -> bool {
227 let (high, _) = self.0.into();
228 (high & (1u128 << 127)) != 0
229 }
230}
231
232impl<const SCALE: u64> ToSql for FixedPoint256<SCALE> {
233 fn to_sql(self, _type_hint: Option<&Type>) -> Result<Value> {
234 Ok(Value::Decimal256(SCALE as usize, self.0))
235 }
236}
237
238impl<const SCALE: u64> FromSql for FixedPoint256<SCALE> {
239 fn from_sql(type_: &Type, value: Value) -> Result<Self> {
240 if !matches!(type_, Type::Decimal256(x) if *x == SCALE as usize) {
241 return Err(unexpected_type(type_));
242 }
243 match value {
244 Value::Decimal256(_, x) => Ok(Self(x)),
245 _ => unimplemented!(),
246 }
247 }
248}
249
250impl<const SCALE: u64> From<i128> for FixedPoint256<SCALE> {
252 fn from(value: i128) -> Self {
253 Self::from_raw(value)
255 }
256}
257
258impl<const SCALE: u64> From<(i128, i32)> for FixedPoint256<SCALE> {
259 fn from(parts: (i128, i32)) -> Self { Self::from_parts(parts.0, parts.1) }
260}
261
262#[cfg(feature = "rust_decimal")]
263impl<const SCALE: u64> From<rust_decimal::Decimal> for FixedPoint256<SCALE> {
264 fn from(decimal: rust_decimal::Decimal) -> Self { Self::from_decimal(decimal) }
265}