Skip to main content

nu_command/math/
ceil.rs

1use crate::math::utils::run_with_elementwise;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MathCeil;
6
7impl Command for MathCeil {
8    fn name(&self) -> &str {
9        "math ceil"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("math ceil")
14            .input_output_types(vec![
15                (Type::Number, Type::Int),
16                (Type::Duration, Type::Duration),
17                (Type::Filesize, Type::Filesize),
18                (
19                    Type::List(Box::new(Type::Number)),
20                    Type::List(Box::new(Type::Int)),
21                ),
22                (
23                    Type::List(Box::new(Type::Duration)),
24                    Type::List(Box::new(Type::Duration)),
25                ),
26                (
27                    Type::List(Box::new(Type::Filesize)),
28                    Type::List(Box::new(Type::Filesize)),
29                ),
30                (Type::Range, Type::List(Box::new(Type::Number))),
31                (Type::record(), Type::record()),
32            ])
33            .rest(
34                "columns",
35                SyntaxShape::CellPath,
36                "The cell-paths/columns to operate on.",
37            )
38            .allow_variants_without_examples(true)
39            .category(Category::Math)
40    }
41
42    fn description(&self) -> &str {
43        "Returns the ceil of a number (smallest integer greater than or equal to that number)."
44    }
45
46    fn extra_description(&self) -> &str {
47        "Filesize and duration values are stored as integers in base units \
48         (bytes and nanoseconds). With no display unit to round against, \
49         `math ceil` is the identity function for those types."
50    }
51
52    fn search_terms(&self) -> Vec<&str> {
53        vec!["ceiling", "round up", "rounding", "integer"]
54    }
55
56    fn is_const(&self) -> bool {
57        true
58    }
59
60    fn run(
61        &self,
62        engine_state: &EngineState,
63        stack: &mut Stack,
64        call: &Call,
65        input: PipelineData,
66    ) -> Result<PipelineData, ShellError> {
67        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
68        let head = call.head;
69        run_with_elementwise(
70            input,
71            cell_paths,
72            head,
73            engine_state.signals(),
74            true,
75            move |value| operate(value, head),
76        )
77    }
78
79    fn run_const(
80        &self,
81        working_set: &StateWorkingSet,
82        call: &Call,
83        input: PipelineData,
84    ) -> Result<PipelineData, ShellError> {
85        let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
86        let head = call.head;
87        run_with_elementwise(
88            input,
89            cell_paths,
90            head,
91            working_set.permanent().signals(),
92            true,
93            move |value| operate(value, head),
94        )
95    }
96
97    fn examples(&self) -> Vec<Example<'_>> {
98        vec![
99            Example {
100                description: "Apply the ceil function to a list of numbers.",
101                example: "[1.5 2.3 -3.1] | math ceil",
102                result: Some(Value::list(
103                    vec![Value::test_int(2), Value::test_int(3), Value::test_int(-3)],
104                    Span::test_data(),
105                )),
106            },
107            Example {
108                description: "Apply ceiling to list-valued columns in a record.",
109                example: "{alice: [1.2 2.7 3.5], bob: [4.1 5.9]} | math ceil",
110                result: Some(Value::test_record(record! {
111                    "alice" => Value::list(
112                        vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
113                        Span::test_data(),
114                    ),
115                    "bob" => Value::list(
116                        vec![Value::test_int(5), Value::test_int(6)],
117                        Span::test_data(),
118                    ),
119                })),
120            },
121            Example {
122                description: "Apply ceiling to a single column using a cell path.",
123                example: "{alice: [1.2 2.7 3.5], bob: [4.1 5.9]} | math ceil alice",
124                result: Some(Value::test_record(record! {
125                    "alice" => Value::list(
126                        vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
127                        Span::test_data(),
128                    ),
129                    "bob" => Value::list(
130                        vec![Value::test_float(4.1), Value::test_float(5.9)],
131                        Span::test_data(),
132                    ),
133                })),
134            },
135            Example {
136                // Filesize is already whole bytes; ceiling cannot use the display unit (KB).
137                description: "Filesize values are already whole bytes, so ceiling is a no-op.",
138                example: "2.1KB | math ceil",
139                result: Some(Value::test_filesize(2100)),
140            },
141        ]
142    }
143}
144
145fn operate(value: Value, head: Span) -> Value {
146    let span = value.span();
147    match value {
148        // Duration and filesize are already integer units (ns / bytes).
149        Value::Int { .. } | Value::Duration { .. } | Value::Filesize { .. } => value,
150        Value::Float { val, .. } => Value::int(val.ceil() as i64, span),
151        Value::Error { .. } => value,
152        other => Value::error(
153            ShellError::OnlySupportsThisInputType {
154                exp_input_type: crate::math::utils::NUMERIC_INPUT_TYPES.into(),
155                wrong_type: other.get_type().to_string(),
156                dst_span: head,
157                src_span: other.span(),
158            },
159            head,
160        ),
161    }
162}
163
164#[cfg(test)]
165mod test {
166    use super::*;
167
168    #[test]
169    fn test_examples() -> nu_test_support::Result {
170        nu_test_support::test().examples(MathCeil)
171    }
172}