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 let PipelineData::Empty = input {
55 return Err(ShellError::PipelineEmpty { dst_span: head });
56 }
57 if let PipelineData::Value(ref v @ Value::Range { ref val, .. }, ..) = input {
58 let span = v.span();
59 ensure_bounded(val, span, head)?;
60 }
61 input.map(
62 move |value| operate(value, head, precision_param),
63 engine_state.signals(),
64 )
65 }
66
67 fn run_const(
68 &self,
69 working_set: &StateWorkingSet,
70 call: &Call,
71 input: PipelineData,
72 ) -> Result<PipelineData, ShellError> {
73 let precision_param: Option<i64> = call.get_flag_const(working_set, "precision")?;
74 let head = call.head;
75 if let PipelineData::Empty = input {
77 return Err(ShellError::PipelineEmpty { dst_span: head });
78 }
79 if let PipelineData::Value(ref v @ Value::Range { ref val, .. }, ..) = input {
80 let span = v.span();
81 ensure_bounded(val, span, head)?;
82 }
83 input.map(
84 move |value| operate(value, head, precision_param),
85 working_set.permanent().signals(),
86 )
87 }
88
89 fn examples(&self) -> Vec<Example<'_>> {
90 vec![
91 Example {
92 description: "Apply the round function to a list of numbers",
93 example: "[1.5 2.3 -3.1] | math round",
94 result: Some(Value::list(
95 vec![Value::test_int(2), Value::test_int(2), Value::test_int(-3)],
96 Span::test_data(),
97 )),
98 },
99 Example {
100 description: "Apply the round function with precision specified",
101 example: "[1.555 2.333 -3.111] | math round --precision 2",
102 result: Some(Value::list(
103 vec![
104 Value::test_float(1.56),
105 Value::test_float(2.33),
106 Value::test_float(-3.11),
107 ],
108 Span::test_data(),
109 )),
110 },
111 Example {
112 description: "Apply negative precision to a list of numbers",
113 example: "[123, 123.3, -123.4] | math round --precision -1",
114 result: Some(Value::list(
115 vec![
116 Value::test_int(120),
117 Value::test_int(120),
118 Value::test_int(-120),
119 ],
120 Span::test_data(),
121 )),
122 },
123 ]
124 }
125}
126
127fn operate(value: Value, head: Span, precision: Option<i64>) -> Value {
128 let span = value.span();
130 let value = if let Value::Int { val, .. } = value {
131 Value::float(val as f64, span)
132 } else {
133 value
134 };
135
136 match value {
137 Value::Float { val, .. } => match precision {
138 Some(precision_number) => Value::float(
139 (val * ((10_f64).powf(precision_number as f64))).round()
140 / (10_f64).powf(precision_number as f64),
141 span,
142 ),
143 None => Value::int(val.round() as i64, span),
144 },
145 Value::Error { .. } => value,
146 other => Value::error(
147 ShellError::OnlySupportsThisInputType {
148 exp_input_type: "numeric".into(),
149 wrong_type: other.get_type().to_string(),
150 dst_span: head,
151 src_span: other.span(),
152 },
153 head,
154 ),
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use super::*;
161
162 #[test]
163 fn test_examples() {
164 use crate::test_examples;
165
166 test_examples(MathRound {})
167 }
168}