nu_command/math/
round.rs

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        // This doesn't match explicit nulls
54        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        // This doesn't match explicit nulls
82        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    // We treat int values as float values in order to avoid code repetition in the match closure
141    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}