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