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 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 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}