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