Skip to main content

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