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