Skip to main content

nu_command/math/
log.rs

1use crate::math::utils::run_with_elementwise;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MathLog;
6
7impl Command for MathLog {
8    fn name(&self) -> &str {
9        "math log"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("math log")
14            .required(
15                "base",
16                SyntaxShape::Number,
17                "Base for which the logarithm should be computed.",
18            )
19            .input_output_types(vec![
20                (Type::Number, Type::Float),
21                (
22                    Type::List(Box::new(Type::Number)),
23                    Type::List(Box::new(Type::Float)),
24                ),
25                (Type::Range, Type::List(Box::new(Type::Number))),
26                (Type::record(), Type::record()),
27            ])
28            .rest(
29                "columns",
30                SyntaxShape::CellPath,
31                "The cell-paths/columns to operate on.",
32            )
33            .allow_variants_without_examples(true)
34            .category(Category::Math)
35    }
36
37    fn description(&self) -> &str {
38        "Returns the logarithm for an arbitrary base."
39    }
40
41    fn search_terms(&self) -> Vec<&str> {
42        vec!["base", "exponent", "inverse", "euler"]
43    }
44
45    fn is_const(&self) -> bool {
46        true
47    }
48
49    fn run(
50        &self,
51        engine_state: &EngineState,
52        stack: &mut Stack,
53        call: &Call,
54        input: PipelineData,
55    ) -> Result<PipelineData, ShellError> {
56        let base = require_positive_base(call.req(engine_state, stack, 0)?, call.head)?;
57        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
58        let head = call.head;
59        run_with_elementwise(
60            input,
61            cell_paths,
62            head,
63            engine_state.signals(),
64            true,
65            move |value| operate(value, head, base),
66        )
67    }
68
69    fn run_const(
70        &self,
71        working_set: &StateWorkingSet,
72        call: &Call,
73        input: PipelineData,
74    ) -> Result<PipelineData, ShellError> {
75        let base = require_positive_base(call.req_const(working_set, 0)?, call.head)?;
76        let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
77        let head = call.head;
78        run_with_elementwise(
79            input,
80            cell_paths,
81            head,
82            working_set.permanent().signals(),
83            true,
84            move |value| operate(value, head, base),
85        )
86    }
87
88    fn examples(&self) -> Vec<Example<'_>> {
89        vec![
90            Example {
91                description: "Get the logarithm of 100 to the base 10.",
92                example: "100 | math log 10",
93                result: Some(Value::test_float(2.0f64)),
94            },
95            Example {
96                example: "[16 8 4] | math log 2",
97                description: "Get the log2 of a list of values.",
98                result: Some(Value::list(
99                    vec![
100                        Value::test_float(4.0),
101                        Value::test_float(3.0),
102                        Value::test_float(2.0),
103                    ],
104                    Span::test_data(),
105                )),
106            },
107            Example {
108                description: "Compute the log base 10 of list-valued columns in a record.",
109                example: "{alice: [1 10 100], bob: [1000 10000]} | math log 10",
110                result: Some(Value::test_record(record! {
111                    "alice" => Value::list(
112                        vec![Value::test_float(0.0), Value::test_float(1.0), Value::test_float(2.0)],
113                        Span::test_data(),
114                    ),
115                    "bob" => Value::list(
116                        vec![Value::test_float(3.0), Value::test_float(4.0)],
117                        Span::test_data(),
118                    ),
119                })),
120            },
121            Example {
122                description: "Compute the log base 10 of a single column using a cell path.",
123                example: "{alice: [1 10 100], bob: [1000 10000]} | math log 10 alice",
124                result: Some(Value::test_record(record! {
125                    "alice" => Value::list(
126                        vec![Value::test_float(0.0), Value::test_float(1.0), Value::test_float(2.0)],
127                        Span::test_data(),
128                    ),
129                    "bob" => Value::list(
130                        vec![Value::test_int(1000), Value::test_int(10000)],
131                        Span::test_data(),
132                    ),
133                })),
134            },
135        ]
136    }
137}
138
139fn require_positive_base(base: Spanned<f64>, head: Span) -> Result<f64, ShellError> {
140    if base.item <= 0.0f64 {
141        return Err(ShellError::UnsupportedInput {
142            msg: "Base has to be greater 0".into(),
143            input: "value originates from here".into(),
144            msg_span: head,
145            input_span: base.span,
146        });
147    }
148    Ok(base.item)
149}
150
151fn operate(value: Value, head: Span, base: f64) -> Value {
152    let span = value.span();
153    match value {
154        numeric @ (Value::Int { .. } | Value::Float { .. }) => {
155            let (val, span) = match numeric {
156                Value::Int { val, .. } => (val as f64, span),
157                Value::Float { val, .. } => (val, span),
158                _ => unreachable!(),
159            };
160
161            if val <= 0.0 {
162                return Value::error(
163                    ShellError::UnsupportedInput {
164                        msg: "'math log' undefined for values outside the open interval (0, Inf)."
165                            .into(),
166                        input: "value originates from here".into(),
167                        msg_span: head,
168                        input_span: span,
169                    },
170                    span,
171                );
172            }
173            // Specialize for better precision/performance
174            let val = if base == 10.0 {
175                val.log10()
176            } else if base == 2.0 {
177                val.log2()
178            } else {
179                val.log(base)
180            };
181
182            Value::float(val, span)
183        }
184        Value::Error { .. } => value,
185        other => Value::error(
186            ShellError::OnlySupportsThisInputType {
187                exp_input_type: crate::math::utils::NUMBER_INPUT_TYPES.into(),
188                wrong_type: other.get_type().to_string(),
189                dst_span: head,
190                src_span: other.span(),
191            },
192            head,
193        ),
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use super::*;
200
201    #[test]
202    fn test_examples() -> nu_test_support::Result {
203        nu_test_support::test().examples(MathLog)
204    }
205}