mz_expr/scalar/func/impls/
float64.rs1use std::fmt;
11
12use chrono::{DateTime, Utc};
13use mz_expr_derive::sqlfunc;
14use mz_ore::cast::TryCastFrom;
15use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
16use mz_repr::adt::timestamp::CheckedTimestamp;
17use mz_repr::{SqlColumnType, SqlScalarType, strconv};
18use serde::{Deserialize, Serialize};
19
20use crate::EvalError;
21use crate::scalar::DomainLimit;
22use crate::scalar::func::EagerUnaryFunc;
23
24#[sqlfunc(
25 sqlname = "-",
26 preserves_uniqueness = false,
27 inverse = to_unary!(NegFloat64),
28 is_monotone = true
29)]
30fn neg_float64(a: f64) -> f64 {
31 -a
32}
33
34#[sqlfunc(sqlname = "abs")]
35fn abs_float64(a: f64) -> f64 {
36 a.abs()
37}
38
39#[sqlfunc(sqlname = "roundf64")]
40fn round_float64(a: f64) -> f64 {
41 a.round_ties_even()
42}
43
44#[sqlfunc(sqlname = "truncf64")]
45fn trunc_float64(a: f64) -> f64 {
46 a.trunc()
47}
48
49#[sqlfunc(sqlname = "ceilf64")]
50fn ceil_float64(a: f64) -> f64 {
51 a.ceil()
52}
53
54#[sqlfunc(sqlname = "floorf64")]
55fn floor_float64(a: f64) -> f64 {
56 a.floor()
57}
58
59#[sqlfunc(
60 sqlname = "double_to_smallint",
61 preserves_uniqueness = false,
62 inverse = to_unary!(super::CastInt16ToFloat64),
63 is_monotone = true
64)]
65fn cast_float64_to_int16(a: f64) -> Result<i16, EvalError> {
66 let f = round_float64(a);
67 #[allow(clippy::as_conversions)]
69 if (f >= (i16::MIN as f64)) && (f < -(i16::MIN as f64)) {
70 Ok(f as i16)
71 } else {
72 Err(EvalError::Int16OutOfRange(f.to_string().into()))
73 }
74}
75
76#[sqlfunc(
77 sqlname = "double_to_integer",
78 preserves_uniqueness = false,
79 inverse = to_unary!(super::CastInt32ToFloat64),
80 is_monotone = true
81)]
82fn cast_float64_to_int32(a: f64) -> Result<i32, EvalError> {
83 let f = round_float64(a);
84 #[allow(clippy::as_conversions)]
90 if (f >= (i32::MIN as f64)) && (f < -(i32::MIN as f64)) {
91 Ok(f as i32)
92 } else {
93 Err(EvalError::Int32OutOfRange(f.to_string().into()))
94 }
95}
96
97#[sqlfunc(
98 sqlname = "f64toi64",
99 preserves_uniqueness = false,
100 inverse = to_unary!(super::CastInt64ToFloat64),
101 is_monotone = true
102)]
103fn cast_float64_to_int64(a: f64) -> Result<i64, EvalError> {
104 let f = round_float64(a);
105 #[allow(clippy::as_conversions)]
111 if (f >= (i64::MIN as f64)) && (f < -(i64::MIN as f64)) {
112 Ok(f as i64)
113 } else {
114 Err(EvalError::Int64OutOfRange(f.to_string().into()))
115 }
116}
117
118#[sqlfunc(
119 sqlname = "double_to_real",
120 preserves_uniqueness = false,
121 inverse = to_unary!(super::CastFloat32ToFloat64),
122 is_monotone = true
123)]
124fn cast_float64_to_float32(a: f64) -> Result<f32, EvalError> {
125 #[allow(clippy::as_conversions)]
127 let result = a as f32;
128 if result.is_infinite() && !a.is_infinite() {
129 Err(EvalError::FloatOverflow)
130 } else if result == 0.0 && a != 0.0 {
131 Err(EvalError::FloatUnderflow)
132 } else {
133 Ok(result)
134 }
135}
136
137#[sqlfunc(
138 sqlname = "double_to_text",
139 preserves_uniqueness = false,
140 inverse = to_unary!(super::CastStringToFloat64)
141)]
142fn cast_float64_to_string(a: f64) -> String {
143 let mut s = String::new();
144 strconv::format_float64(&mut s, a);
145 s
146}
147
148#[sqlfunc(
149 sqlname = "double_to_uint2",
150 preserves_uniqueness = false,
151 inverse = to_unary!(super::CastUint16ToFloat64),
152 is_monotone = true
153)]
154fn cast_float64_to_uint16(a: f64) -> Result<u16, EvalError> {
155 let f = round_float64(a);
156 #[allow(clippy::as_conversions)]
158 if (f >= 0.0) && (f <= (u16::MAX as f64)) {
159 Ok(f as u16)
160 } else {
161 Err(EvalError::UInt16OutOfRange(f.to_string().into()))
162 }
163}
164
165#[sqlfunc(
166 sqlname = "double_to_uint4",
167 preserves_uniqueness = false,
168 inverse = to_unary!(super::CastUint32ToFloat64),
169 is_monotone = true
170)]
171fn cast_float64_to_uint32(a: f64) -> Result<u32, EvalError> {
172 let f = round_float64(a);
173 #[allow(clippy::as_conversions)]
175 if (f >= 0.0) && (f <= (u32::MAX as f64)) {
176 Ok(f as u32)
177 } else {
178 Err(EvalError::UInt32OutOfRange(f.to_string().into()))
179 }
180}
181
182#[sqlfunc(
183 sqlname = "double_to_uint8",
184 preserves_uniqueness = false,
185 inverse = to_unary!(super::CastUint64ToFloat64),
186 is_monotone = true
187)]
188fn cast_float64_to_uint64(a: f64) -> Result<u64, EvalError> {
189 let f = round_float64(a);
190 #[allow(clippy::as_conversions)]
196 if (f >= 0.0) && (f < (u64::MAX as f64)) {
197 Ok(f as u64)
198 } else {
199 Err(EvalError::UInt64OutOfRange(f.to_string().into()))
200 }
201}
202
203#[derive(
204 Ord,
205 PartialOrd,
206 Clone,
207 Debug,
208 Eq,
209 PartialEq,
210 Serialize,
211 Deserialize,
212 Hash
213)]
214pub struct CastFloat64ToNumeric(pub Option<NumericMaxScale>);
215
216impl EagerUnaryFunc for CastFloat64ToNumeric {
217 type Input<'a> = f64;
218 type Output<'a> = Result<Numeric, EvalError>;
219
220 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
221 if a.is_infinite() {
222 return Err(EvalError::InfinityOutOfDomain(
223 "casting double precision to numeric".into(),
224 ));
225 }
226 let mut a = Numeric::from(a);
227 if let Some(scale) = self.0 {
228 if numeric::rescale(&mut a, scale.into_u8()).is_err() {
229 return Err(EvalError::NumericFieldOverflow);
230 }
231 }
232 match numeric::munge_numeric(&mut a) {
233 Ok(_) => Ok(a),
234 Err(_) => Err(EvalError::NumericFieldOverflow),
235 }
236 }
237
238 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
239 SqlScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
240 }
241
242 fn inverse(&self) -> Option<crate::UnaryFunc> {
243 to_unary!(super::CastNumericToFloat64)
244 }
245
246 fn is_monotone(&self) -> bool {
247 true
248 }
249}
250
251impl fmt::Display for CastFloat64ToNumeric {
252 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253 f.write_str("double_to_numeric")
254 }
255}
256
257#[sqlfunc(sqlname = "sqrtf64")]
258fn sqrt_float64(a: f64) -> Result<f64, EvalError> {
259 if a < 0.0 {
260 return Err(EvalError::NegSqrt);
261 }
262 Ok(a.sqrt())
263}
264
265#[sqlfunc(sqlname = "cbrtf64")]
266fn cbrt_float64(a: f64) -> f64 {
267 a.cbrt()
268}
269
270#[sqlfunc]
271fn cos(a: f64) -> Result<f64, EvalError> {
272 if a.is_infinite() {
273 return Err(EvalError::InfinityOutOfDomain("cos".into()));
274 }
275 Ok(a.cos())
276}
277
278#[sqlfunc]
279fn acos(a: f64) -> Result<f64, EvalError> {
280 if a < -1.0 || 1.0 < a {
281 return Err(EvalError::OutOfDomain(
282 DomainLimit::Inclusive(-1),
283 DomainLimit::Inclusive(1),
284 "acos".into(),
285 ));
286 }
287 Ok(a.acos())
288}
289
290#[sqlfunc]
291fn cosh(a: f64) -> f64 {
292 a.cosh()
293}
294
295#[sqlfunc]
296fn acosh(a: f64) -> Result<f64, EvalError> {
297 if a < 1.0 {
298 return Err(EvalError::OutOfDomain(
299 DomainLimit::Inclusive(1),
300 DomainLimit::None,
301 "acosh".into(),
302 ));
303 }
304 Ok(a.acosh())
305}
306
307#[sqlfunc]
308fn sin(a: f64) -> Result<f64, EvalError> {
309 if a.is_infinite() {
310 return Err(EvalError::InfinityOutOfDomain("sin".into()));
311 }
312 Ok(a.sin())
313}
314
315#[sqlfunc]
316fn asin(a: f64) -> Result<f64, EvalError> {
317 if a < -1.0 || 1.0 < a {
318 return Err(EvalError::OutOfDomain(
319 DomainLimit::Inclusive(-1),
320 DomainLimit::Inclusive(1),
321 "asin".into(),
322 ));
323 }
324 Ok(a.asin())
325}
326
327#[sqlfunc]
328fn sinh(a: f64) -> f64 {
329 a.sinh()
330}
331
332#[sqlfunc]
333fn asinh(a: f64) -> f64 {
334 a.asinh()
335}
336
337#[sqlfunc]
338fn tan(a: f64) -> Result<f64, EvalError> {
339 if a.is_infinite() {
340 return Err(EvalError::InfinityOutOfDomain("tan".into()));
341 }
342 Ok(a.tan())
343}
344
345#[sqlfunc]
346fn atan(a: f64) -> f64 {
347 a.atan()
348}
349
350#[sqlfunc]
351fn tanh(a: f64) -> f64 {
352 a.tanh()
353}
354
355#[sqlfunc]
356fn atanh(a: f64) -> Result<f64, EvalError> {
357 if a < -1.0 || 1.0 < a {
358 return Err(EvalError::OutOfDomain(
359 DomainLimit::Inclusive(-1),
360 DomainLimit::Inclusive(1),
361 "atanh".into(),
362 ));
363 }
364 Ok(a.atanh())
365}
366
367#[sqlfunc]
368fn cot(a: f64) -> Result<f64, EvalError> {
369 if a.is_infinite() {
370 return Err(EvalError::InfinityOutOfDomain("cot".into()));
371 }
372 Ok(1.0 / a.tan())
373}
374
375#[sqlfunc]
376fn radians(a: f64) -> f64 {
377 a.to_radians()
378}
379
380#[sqlfunc]
381fn degrees(a: f64) -> f64 {
382 a.to_degrees()
383}
384
385#[sqlfunc(sqlname = "log10f64")]
386fn log10(a: f64) -> Result<f64, EvalError> {
387 if a.is_sign_negative() {
388 return Err(EvalError::NegativeOutOfDomain("log10".into()));
389 }
390 if a == 0.0 {
391 return Err(EvalError::ZeroOutOfDomain("log10".into()));
392 }
393 Ok(a.log10())
394}
395
396#[sqlfunc(sqlname = "lnf64")]
397fn ln(a: f64) -> Result<f64, EvalError> {
398 if a.is_sign_negative() {
399 return Err(EvalError::NegativeOutOfDomain("ln".into()));
400 }
401 if a == 0.0 {
402 return Err(EvalError::ZeroOutOfDomain("ln".into()));
403 }
404 Ok(a.ln())
405}
406
407#[sqlfunc(sqlname = "expf64")]
408fn exp(a: f64) -> Result<f64, EvalError> {
409 let r = a.exp();
410 if r.is_infinite() {
411 return Err(EvalError::FloatOverflow);
412 }
413 if r == 0.0 {
414 return Err(EvalError::FloatUnderflow);
415 }
416 Ok(r)
417}
418
419#[sqlfunc(sqlname = "mz_sleep")]
420fn sleep(a: f64) -> Result<Option<CheckedTimestamp<DateTime<Utc>>>, EvalError> {
421 let duration = std::time::Duration::try_from_secs_f64(a).map_err(|_| {
422 let mut val = String::new();
423 strconv::format_float64(&mut val, a);
424 EvalError::InvalidParameterValue(format!("cannot sleep for {val} seconds").into())
425 })?;
426 std::thread::sleep(duration);
427 Ok(None)
428}
429
430#[sqlfunc(sqlname = "tots")]
431fn to_timestamp(f: f64) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
432 const NANO_SECONDS_PER_SECOND: i64 = 1_000_000_000;
433 if f.is_nan() {
434 Err(EvalError::TimestampCannotBeNan)
435 } else if f.is_infinite() {
436 Err(EvalError::TimestampOutOfRange)
438 } else {
439 let mut secs = i64::try_cast_from(f.trunc()).ok_or(EvalError::TimestampOutOfRange)?;
440 let microsecs = (f.fract() * 1_000_000.0).round();
446 let mut nanosecs =
447 i64::try_cast_from(microsecs * 1_000.0).ok_or(EvalError::TimestampOutOfRange)?;
448 if nanosecs < 0 {
449 secs = secs.checked_sub(1).ok_or(EvalError::TimestampOutOfRange)?;
450 nanosecs = NANO_SECONDS_PER_SECOND
451 .checked_add(nanosecs)
452 .ok_or(EvalError::TimestampOutOfRange)?;
453 }
454 secs = secs
456 .checked_add(nanosecs / NANO_SECONDS_PER_SECOND)
457 .ok_or(EvalError::TimestampOutOfRange)?;
458 nanosecs %= NANO_SECONDS_PER_SECOND;
459 let nanosecs = u32::try_from(nanosecs).map_err(|_| EvalError::TimestampOutOfRange)?;
460 match DateTime::from_timestamp(secs, nanosecs) {
461 Some(dt) => {
462 CheckedTimestamp::from_timestamplike(dt).map_err(|_| EvalError::TimestampOutOfRange)
463 }
464 None => Err(EvalError::TimestampOutOfRange),
465 }
466 }
467}