nu_command/math/
log.rs

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