mz_expr/scalar/func/impls/
float32.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::fmt;
11
12use mz_lowertest::MzReflect;
13use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
14use mz_repr::{ColumnType, ScalarType, strconv};
15use serde::{Deserialize, Serialize};
16
17use crate::EvalError;
18use crate::scalar::func::EagerUnaryFunc;
19
20sqlfunc!(
21    #[sqlname = "-"]
22    #[preserves_uniqueness = false]
23    #[inverse = to_unary!(NegFloat32)]
24    #[is_monotone = true]
25    fn neg_float32(a: f32) -> f32 {
26        -a
27    }
28);
29
30sqlfunc!(
31    #[sqlname = "abs"]
32    fn abs_float32(a: f32) -> f32 {
33        a.abs()
34    }
35);
36
37sqlfunc!(
38    #[sqlname = "roundf32"]
39    fn round_float32(a: f32) -> f32 {
40        a.round_ties_even()
41    }
42);
43
44sqlfunc!(
45    #[sqlname = "truncf32"]
46    fn trunc_float32(a: f32) -> f32 {
47        a.trunc()
48    }
49);
50
51sqlfunc!(
52    #[sqlname = "ceilf32"]
53    fn ceil_float32(a: f32) -> f32 {
54        a.ceil()
55    }
56);
57
58sqlfunc!(
59    #[sqlname = "floorf32"]
60    fn floor_float32(a: f32) -> f32 {
61        a.floor()
62    }
63);
64
65sqlfunc!(
66    #[sqlname = "real_to_smallint"]
67    #[preserves_uniqueness = false]
68    #[inverse = to_unary!(super::CastInt16ToFloat32)]
69    #[is_monotone = true]
70    fn cast_float32_to_int16(a: f32) -> Result<i16, EvalError> {
71        let f = round_float32(a);
72        // TODO(benesch): remove potentially dangerous usage of `as`.
73        #[allow(clippy::as_conversions)]
74        if (f >= (i16::MIN as f32)) && (f < -(i16::MIN as f32)) {
75            Ok(f as i16)
76        } else {
77            Err(EvalError::Int16OutOfRange(f.to_string().into()))
78        }
79    }
80);
81
82sqlfunc!(
83    #[sqlname = "real_to_integer"]
84    #[preserves_uniqueness = false]
85    #[inverse = to_unary!(super::CastInt32ToFloat32)]
86    #[is_monotone = true]
87    fn cast_float32_to_int32(a: f32) -> Result<i32, EvalError> {
88        let f = round_float32(a);
89        // This condition is delicate because i32::MIN can be represented exactly by
90        // an f32 but not i32::MAX. We follow PostgreSQL's approach here.
91        //
92        // See: https://github.com/postgres/postgres/blob/ca3b37487/src/include/c.h#L1074-L1096
93        // TODO(benesch): remove potentially dangerous usage of `as`.
94        #[allow(clippy::as_conversions)]
95        if (f >= (i32::MIN as f32)) && (f < -(i32::MIN as f32)) {
96            Ok(f as i32)
97        } else {
98            Err(EvalError::Int32OutOfRange(f.to_string().into()))
99        }
100    }
101);
102
103sqlfunc!(
104    #[sqlname = "real_to_bigint"]
105    #[preserves_uniqueness = false]
106    #[inverse = to_unary!(super::CastInt64ToFloat32)]
107    #[is_monotone = true]
108    fn cast_float32_to_int64(a: f32) -> Result<i64, EvalError> {
109        let f = round_float32(a);
110        // This condition is delicate because i64::MIN can be represented exactly by
111        // an f32 but not i64::MAX. We follow PostgreSQL's approach here.
112        //
113        // See: https://github.com/postgres/postgres/blob/ca3b37487/src/include/c.h#L1074-L1096
114        // TODO(benesch): remove potentially dangerous usage of `as`.
115        #[allow(clippy::as_conversions)]
116        if (f >= (i64::MIN as f32)) && (f < -(i64::MIN as f32)) {
117            Ok(f as i64)
118        } else {
119            Err(EvalError::Int64OutOfRange(f.to_string().into()))
120        }
121    }
122);
123
124sqlfunc!(
125    #[sqlname = "real_to_double"]
126    #[preserves_uniqueness = false]
127    #[inverse = to_unary!(super::CastFloat64ToFloat32)]
128    #[is_monotone = true]
129    fn cast_float32_to_float64(a: f32) -> f64 {
130        a.into()
131    }
132);
133
134sqlfunc!(
135    #[sqlname = "real_to_text"]
136    #[preserves_uniqueness = false]
137    #[inverse = to_unary!(super::CastStringToFloat32)]
138    fn cast_float32_to_string(a: f32) -> String {
139        let mut s = String::new();
140        strconv::format_float32(&mut s, a);
141        s
142    }
143);
144
145sqlfunc!(
146    #[sqlname = "real_to_uint2"]
147    #[preserves_uniqueness = false]
148    #[inverse = to_unary!(super::CastUint16ToFloat32)]
149    #[is_monotone = true]
150    fn cast_float32_to_uint16(a: f32) -> Result<u16, EvalError> {
151        let f = round_float32(a);
152        // TODO(benesch): remove potentially dangerous usage of `as`.
153        #[allow(clippy::as_conversions)]
154        if (f >= 0.0) && (f <= (u16::MAX as f32)) {
155            Ok(f as u16)
156        } else {
157            Err(EvalError::UInt16OutOfRange(f.to_string().into()))
158        }
159    }
160);
161
162sqlfunc!(
163    #[sqlname = "real_to_uint4"]
164    #[preserves_uniqueness = false]
165    #[inverse = to_unary!(super::CastUint32ToFloat32)]
166    #[is_monotone = true]
167    fn cast_float32_to_uint32(a: f32) -> Result<u32, EvalError> {
168        let f = round_float32(a);
169        // TODO(benesch): remove potentially dangerous usage of `as`.
170        #[allow(clippy::as_conversions)]
171        if (f >= 0.0) && (f <= (u32::MAX as f32)) {
172            Ok(f as u32)
173        } else {
174            Err(EvalError::UInt32OutOfRange(f.to_string().into()))
175        }
176    }
177);
178
179sqlfunc!(
180    #[sqlname = "real_to_uint8"]
181    #[preserves_uniqueness = false]
182    #[inverse = to_unary!(super::CastUint64ToFloat32)]
183    #[is_monotone = true]
184    fn cast_float32_to_uint64(a: f32) -> Result<u64, EvalError> {
185        let f = round_float32(a);
186        // TODO(benesch): remove potentially dangerous usage of `as`.
187        #[allow(clippy::as_conversions)]
188        if (f >= 0.0) && (f <= (u64::MAX as f32)) {
189            Ok(f as u64)
190        } else {
191            Err(EvalError::UInt64OutOfRange(f.to_string().into()))
192        }
193    }
194);
195
196#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
197pub struct CastFloat32ToNumeric(pub Option<NumericMaxScale>);
198
199impl<'a> EagerUnaryFunc<'a> for CastFloat32ToNumeric {
200    type Input = f32;
201    type Output = Result<Numeric, EvalError>;
202
203    fn call(&self, a: f32) -> Result<Numeric, EvalError> {
204        if a.is_infinite() {
205            return Err(EvalError::InfinityOutOfDomain(
206                "casting real to numeric".into(),
207            ));
208        }
209        let mut a = Numeric::from(a);
210        if let Some(scale) = self.0 {
211            if numeric::rescale(&mut a, scale.into_u8()).is_err() {
212                return Err(EvalError::NumericFieldOverflow);
213            }
214        }
215        numeric::munge_numeric(&mut a).unwrap();
216        Ok(a)
217    }
218
219    fn output_type(&self, input: ColumnType) -> ColumnType {
220        ScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
221    }
222
223    fn inverse(&self) -> Option<crate::UnaryFunc> {
224        to_unary!(super::CastNumericToFloat32)
225    }
226
227    fn is_monotone(&self) -> bool {
228        true
229    }
230}
231
232impl fmt::Display for CastFloat32ToNumeric {
233    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234        f.write_str("real_to_numeric")
235    }
236}