nu_command/math/
log.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::Signals;
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            ])
26            .allow_variants_without_examples(true)
27            .category(Category::Math)
28    }
29
30    fn description(&self) -> &str {
31        "Returns the logarithm for an arbitrary base."
32    }
33
34    fn search_terms(&self) -> Vec<&str> {
35        vec!["base", "exponent", "inverse", "euler"]
36    }
37
38    fn is_const(&self) -> bool {
39        true
40    }
41
42    fn run(
43        &self,
44        engine_state: &EngineState,
45        stack: &mut Stack,
46        call: &Call,
47        input: PipelineData,
48    ) -> Result<PipelineData, ShellError> {
49        let base: Spanned<f64> = call.req(engine_state, stack, 0)?;
50        log(base, call.head, input, engine_state.signals())
51    }
52
53    fn run_const(
54        &self,
55        working_set: &StateWorkingSet,
56        call: &Call,
57        input: PipelineData,
58    ) -> Result<PipelineData, ShellError> {
59        let base: Spanned<f64> = call.req_const(working_set, 0)?;
60        log(base, call.head, input, working_set.permanent().signals())
61    }
62
63    fn examples(&self) -> Vec<Example> {
64        vec![
65            Example {
66                description: "Get the logarithm of 100 to the base 10",
67                example: "100 | math log 10",
68                result: Some(Value::test_float(2.0f64)),
69            },
70            Example {
71                example: "[16 8 4] | math log 2",
72                description: "Get the log2 of a list of values",
73                result: Some(Value::list(
74                    vec![
75                        Value::test_float(4.0),
76                        Value::test_float(3.0),
77                        Value::test_float(2.0),
78                    ],
79                    Span::test_data(),
80                )),
81            },
82        ]
83    }
84}
85
86fn log(
87    base: Spanned<f64>,
88    head: Span,
89    input: PipelineData,
90    signals: &Signals,
91) -> Result<PipelineData, ShellError> {
92    if base.item <= 0.0f64 {
93        return Err(ShellError::UnsupportedInput {
94            msg: "Base has to be greater 0".into(),
95            input: "value originates from here".into(),
96            msg_span: head,
97            input_span: base.span,
98        });
99    }
100    // This doesn't match explicit nulls
101    if matches!(input, PipelineData::Empty) {
102        return Err(ShellError::PipelineEmpty { dst_span: head });
103    }
104    let base = base.item;
105    input.map(move |value| operate(value, head, base), signals)
106}
107
108fn operate(value: Value, head: Span, base: f64) -> Value {
109    let span = value.span();
110    match value {
111        numeric @ (Value::Int { .. } | Value::Float { .. }) => {
112            let (val, span) = match numeric {
113                Value::Int { val, .. } => (val as f64, span),
114                Value::Float { val, .. } => (val, span),
115                _ => unreachable!(),
116            };
117
118            if val <= 0.0 {
119                return Value::error(
120                    ShellError::UnsupportedInput {
121                        msg: "'math log' undefined for values outside the open interval (0, Inf)."
122                            .into(),
123                        input: "value originates from here".into(),
124                        msg_span: head,
125                        input_span: span,
126                    },
127                    span,
128                );
129            }
130            // Specialize for better precision/performance
131            let val = if base == 10.0 {
132                val.log10()
133            } else if base == 2.0 {
134                val.log2()
135            } else {
136                val.log(base)
137            };
138
139            Value::float(val, span)
140        }
141        Value::Error { .. } => value,
142        other => Value::error(
143            ShellError::OnlySupportsThisInputType {
144                exp_input_type: "numeric".into(),
145                wrong_type: other.get_type().to_string(),
146                dst_span: head,
147                src_span: other.span(),
148            },
149            head,
150        ),
151    }
152}
153
154#[cfg(test)]
155mod test {
156    use super::*;
157
158    #[test]
159    fn test_examples() {
160        use crate::test_examples;
161
162        test_examples(MathLog {})
163    }
164}