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