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