1use crate::compiler::prelude::*;
2
3use super::util::round_to_precision;
4use std::sync::LazyLock;
5
6static DEFAULT_PRECISION: LazyLock<Value> = LazyLock::new(|| Value::Integer(0));
7
8static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
9 vec![
10 Parameter::required(
11 "value",
12 kind::FLOAT | kind::INTEGER,
13 "The number to round down.",
14 ),
15 Parameter::optional(
16 "precision",
17 kind::INTEGER,
18 "The number of decimal places to round to.",
19 )
20 .default(&DEFAULT_PRECISION),
21 ]
22});
23
24fn floor(precision: Value, value: Value) -> Resolved {
25 let precision = precision.try_integer()?;
26
27 match value {
28 Value::Float(f) => Ok(Value::from_f64_or_zero(round_to_precision(
29 *f,
30 precision,
31 f64::floor,
32 ))),
33 value @ Value::Integer(_) => Ok(value),
34 value => Err(ValueError::Expected {
35 got: value.kind(),
36 expected: Kind::float() | Kind::integer(),
37 }
38 .into()),
39 }
40}
41
42#[derive(Clone, Copy, Debug)]
43pub struct Floor;
44
45impl Function for Floor {
46 fn identifier(&self) -> &'static str {
47 "floor"
48 }
49
50 fn usage(&self) -> &'static str {
51 "Rounds the `value` down to the specified `precision`."
52 }
53
54 fn category(&self) -> &'static str {
55 Category::Number.as_ref()
56 }
57
58 fn return_kind(&self) -> u16 {
59 kind::INTEGER | kind::FLOAT
60 }
61
62 fn return_rules(&self) -> &'static [&'static str] {
63 &[
64 "Returns an integer if `precision` is `0` (this is the default). Returns a float otherwise.",
65 ]
66 }
67
68 fn parameters(&self) -> &'static [Parameter] {
69 PARAMETERS.as_slice()
70 }
71
72 fn compile(
73 &self,
74 _state: &state::TypeState,
75 _ctx: &mut FunctionCompileContext,
76 arguments: ArgumentList,
77 ) -> Compiled {
78 let value = arguments.required("value");
79 let precision = arguments.optional("precision");
80
81 Ok(FloorFn { value, precision }.as_expr())
82 }
83
84 fn examples(&self) -> &'static [Example] {
85 &[
86 example! {
87 title: "Round a number down (without precision)",
88 source: "floor(9.8)",
89 result: Ok("9.0"),
90 },
91 example! {
92 title: "Round a number down (with precision)",
93 source: "floor(4.345, precision: 2)",
94 result: Ok("4.34"),
95 },
96 ]
97 }
98}
99
100#[derive(Clone, Debug)]
101struct FloorFn {
102 value: Box<dyn Expression>,
103 precision: Option<Box<dyn Expression>>,
104}
105
106impl FunctionExpression for FloorFn {
107 fn resolve(&self, ctx: &mut Context) -> Resolved {
108 let precision = self
109 .precision
110 .map_resolve_with_default(ctx, || DEFAULT_PRECISION.clone())?;
111 let value = self.value.resolve(ctx)?;
112
113 floor(precision, value)
114 }
115
116 fn type_def(&self, state: &state::TypeState) -> TypeDef {
117 match Kind::from(self.value.type_def(state)) {
118 v if v.is_float() || v.is_integer() => v.into(),
119 _ => Kind::integer().or_float().into(),
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::value;
128
129 test_function![
130 floor => Floor;
131
132 lower {
133 args: func_args![value: 1234.2],
134 want: Ok(value!(1234.0)),
135 tdef: TypeDef::float(),
136 }
137
138 higher {
139 args: func_args![value: 1234.8],
140 want: Ok(value!(1234.0)),
141 tdef: TypeDef::float(),
142 }
143
144 exact {
145 args: func_args![value: 1234],
146 want: Ok(value!(1234)),
147 tdef: TypeDef::integer(),
148 }
149
150 precision {
151 args: func_args![value: 1234.39429,
152 precision: 1],
153 want: Ok(value!(1234.3)),
154 tdef: TypeDef::float(),
155 }
156
157 bigger_precision {
158 args: func_args![value: 1234.56789,
159 precision: 4],
160 want: Ok(value!(1234.5678)),
161 tdef: TypeDef::float(),
162 }
163
164 huge_number {
165 args: func_args![value: 9_876_543_210_123_456_789_098_765_432_101_234_567_890_987_654_321.987_654_321,
166 precision: 5],
167 want: Ok(value!(9_876_543_210_123_456_789_098_765_432_101_234_567_890_987_654_321.987_65)),
168 tdef: TypeDef::float(),
169 }
170 ];
171}