1use crate::math::utils::ensure_bounded;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MathRound;
6
7impl Command for MathRound {
8 fn name(&self) -> &str {
9 "math round"
10 }
11
12 fn signature(&self) -> Signature {
13 Signature::build("math round")
14 .input_output_types(vec![
15 (Type::Number, Type::Number),
16 (
17 Type::List(Box::new(Type::Number)),
18 Type::List(Box::new(Type::Number)),
19 ),
20 (Type::Range, Type::List(Box::new(Type::Number))),
21 ])
22 .allow_variants_without_examples(true)
23 .named(
24 "precision",
25 SyntaxShape::Number,
26 "digits of precision",
27 Some('p'),
28 )
29 .category(Category::Math)
30 }
31
32 fn description(&self) -> &str {
33 "Returns the input number rounded to the specified precision."
34 }
35
36 fn search_terms(&self) -> Vec<&str> {
37 vec!["approx", "closest", "nearest"]
38 }
39
40 fn is_const(&self) -> bool {
41 true
42 }
43
44 fn run(
45 &self,
46 engine_state: &EngineState,
47 stack: &mut Stack,
48 call: &Call,
49 input: PipelineData,
50 ) -> Result<PipelineData, ShellError> {
51 let precision_param: Option<i64> = call.get_flag(engine_state, stack, "precision")?;
52 let head = call.head;
53 if matches!(input, PipelineData::Empty) {
55 return Err(ShellError::PipelineEmpty { dst_span: head });
56 }
57 if let PipelineData::Value(
58 Value::Range {
59 ref val,
60 internal_span,
61 },
62 ..,
63 ) = input
64 {
65 ensure_bounded(val.as_ref(), internal_span, head)?;
66 }
67 input.map(
68 move |value| operate(value, head, precision_param),
69 engine_state.signals(),
70 )
71 }
72
73 fn run_const(
74 &self,
75 working_set: &StateWorkingSet,
76 call: &Call,
77 input: PipelineData,
78 ) -> Result<PipelineData, ShellError> {
79 let precision_param: Option<i64> = call.get_flag_const(working_set, "precision")?;
80 let head = call.head;
81 if matches!(input, PipelineData::Empty) {
83 return Err(ShellError::PipelineEmpty { dst_span: head });
84 }
85 if let PipelineData::Value(
86 Value::Range {
87 ref val,
88 internal_span,
89 },
90 ..,
91 ) = input
92 {
93 ensure_bounded(val.as_ref(), internal_span, head)?;
94 }
95 input.map(
96 move |value| operate(value, head, precision_param),
97 working_set.permanent().signals(),
98 )
99 }
100
101 fn examples(&self) -> Vec<Example> {
102 vec![
103 Example {
104 description: "Apply the round function to a list of numbers",
105 example: "[1.5 2.3 -3.1] | math round",
106 result: Some(Value::list(
107 vec![Value::test_int(2), Value::test_int(2), Value::test_int(-3)],
108 Span::test_data(),
109 )),
110 },
111 Example {
112 description: "Apply the round function with precision specified",
113 example: "[1.555 2.333 -3.111] | math round --precision 2",
114 result: Some(Value::list(
115 vec![
116 Value::test_float(1.56),
117 Value::test_float(2.33),
118 Value::test_float(-3.11),
119 ],
120 Span::test_data(),
121 )),
122 },
123 Example {
124 description: "Apply negative precision to a list of numbers",
125 example: "[123, 123.3, -123.4] | math round --precision -1",
126 result: Some(Value::list(
127 vec![
128 Value::test_int(120),
129 Value::test_int(120),
130 Value::test_int(-120),
131 ],
132 Span::test_data(),
133 )),
134 },
135 ]
136 }
137}
138
139fn operate(value: Value, head: Span, precision: Option<i64>) -> Value {
140 let span = value.span();
142 let value = if let Value::Int { val, .. } = value {
143 Value::float(val as f64, span)
144 } else {
145 value
146 };
147
148 match value {
149 Value::Float { val, .. } => match precision {
150 Some(precision_number) => Value::float(
151 (val * ((10_f64).powf(precision_number as f64))).round()
152 / (10_f64).powf(precision_number as f64),
153 span,
154 ),
155 None => Value::int(val.round() as i64, span),
156 },
157 Value::Error { .. } => value,
158 other => Value::error(
159 ShellError::OnlySupportsThisInputType {
160 exp_input_type: "numeric".into(),
161 wrong_type: other.get_type().to_string(),
162 dst_span: head,
163 src_span: other.span(),
164 },
165 head,
166 ),
167 }
168}
169
170#[cfg(test)]
171mod test {
172 use super::*;
173
174 #[test]
175 fn test_examples() {
176 use crate::test_examples;
177
178 test_examples(MathRound {})
179 }
180}