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