From b27d6b2cb18578f049d374a24028ba5ddf3f22db Mon Sep 17 00:00:00 2001 From: sholderbach Date: Sun, 27 Nov 2022 16:28:27 +0100 Subject: [PATCH] Add basic trigonometric functions (#7258) `math sin` `math cos` `math tan` Support degrees with the flag `--degrees`/`-d` --- crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/math/cos.rs | 107 +++++++++++++++++++++++ crates/nu-command/src/math/mod.rs | 7 ++ crates/nu-command/src/math/sin.rs | 107 +++++++++++++++++++++++ crates/nu-command/src/math/tan.rs | 105 ++++++++++++++++++++++ 5 files changed, 329 insertions(+) create mode 100644 crates/nu-command/src/math/cos.rs create mode 100644 crates/nu-command/src/math/sin.rs create mode 100644 crates/nu-command/src/math/tan.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 7377c6b4e..e9920f9f0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -405,6 +405,9 @@ pub fn create_default_context() -> EngineState { MathStddev, MathSum, MathVariance, + MathSin, + MathCos, + MathTan, MathPi, MathEuler, }; diff --git a/crates/nu-command/src/math/cos.rs b/crates/nu-command/src/math/cos.rs new file mode 100644 index 000000000..d986165f7 --- /dev/null +++ b/crates/nu-command/src/math/cos.rs @@ -0,0 +1,107 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math cos" + } + + fn signature(&self) -> Signature { + Signature::build("math cos") + .switch("degrees", "Use degrees instead of radians", Some('d')) + .input_output_types(vec![(Type::Number, Type::Float)]) + .vectorizes_over_list(true) + .category(Category::Math) + } + + fn usage(&self) -> &str { + "Returns the cosine of the number." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["trigonometry"] + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let use_degrees = call.has_flag("degrees"); + input.map( + move |value| operate(value, head, use_degrees), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Apply the cosine to pi", + example: "math pi | math cos", + result: Some(Value::test_float(-1f64)), + }, + Example { + description: "Apply the cosine to a list of angles in degrees", + example: "[0 90 180 270 360] | math cos -d", + result: Some(Value::List { + vals: vec![ + Value::test_float(1f64), + Value::test_float(0f64), + Value::test_float(-1f64), + Value::test_float(0f64), + Value::test_float(1f64), + ], + span: Span::test_data(), + }), + }, + ] + } +} + +fn operate(value: Value, head: Span, use_degrees: bool) -> Value { + match value { + numeric @ (Value::Int { .. } | Value::Float { .. }) => { + let (val, span) = match numeric { + Value::Int { val, span } => (val as f64, span), + Value::Float { val, span } => (val, span), + _ => unreachable!(), + }; + + let val = if use_degrees { val.to_radians() } else { val }; + + Value::Float { + val: val.cos(), + span, + } + } + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Only numerical values are supported, input type: {:?}", + other.get_type() + ), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index fd0c2b8b6..dfba8495b 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -1,6 +1,7 @@ mod abs; mod avg; mod ceil; +mod cos; mod euler; mod eval; mod floor; @@ -13,9 +14,11 @@ mod pi; mod product; mod reducers; mod round; +mod sin; mod sqrt; mod stddev; mod sum; +mod tan; mod utils; mod variance; @@ -36,5 +39,9 @@ pub use stddev::SubCommand as MathStddev; pub use sum::SubCommand as MathSum; pub use variance::SubCommand as MathVariance; +pub use cos::SubCommand as MathCos; +pub use sin::SubCommand as MathSin; +pub use tan::SubCommand as MathTan; + pub use euler::SubCommand as MathEuler; pub use pi::SubCommand as MathPi; diff --git a/crates/nu-command/src/math/sin.rs b/crates/nu-command/src/math/sin.rs new file mode 100644 index 000000000..1947a690d --- /dev/null +++ b/crates/nu-command/src/math/sin.rs @@ -0,0 +1,107 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math sin" + } + + fn signature(&self) -> Signature { + Signature::build("math sin") + .switch("degrees", "Use degrees instead of radians", Some('d')) + .input_output_types(vec![(Type::Number, Type::Float)]) + .vectorizes_over_list(true) + .category(Category::Math) + } + + fn usage(&self) -> &str { + "Returns the sine of the number." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["trigonometry"] + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let use_degrees = call.has_flag("degrees"); + input.map( + move |value| operate(value, head, use_degrees), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Apply the sine to π/2", + example: "(math pi) / 2 | math sin", + result: Some(Value::test_float(1f64)), + }, + Example { + description: "Apply the sine to a list of angles in degrees", + example: "[0 90 180 270 360] | math sin -d | math round --precision 4", + result: Some(Value::List { + vals: vec![ + Value::test_float(0f64), + Value::test_float(1f64), + Value::test_float(0f64), + Value::test_float(-1f64), + Value::test_float(0f64), + ], + span: Span::test_data(), + }), + }, + ] + } +} + +fn operate(value: Value, head: Span, use_degrees: bool) -> Value { + match value { + numeric @ (Value::Int { .. } | Value::Float { .. }) => { + let (val, span) = match numeric { + Value::Int { val, span } => (val as f64, span), + Value::Float { val, span } => (val, span), + _ => unreachable!(), + }; + + let val = if use_degrees { val.to_radians() } else { val }; + + Value::Float { + val: val.sin(), + span, + } + } + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Only numerical values are supported, input type: {:?}", + other.get_type() + ), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/tan.rs b/crates/nu-command/src/math/tan.rs new file mode 100644 index 000000000..c1b400b54 --- /dev/null +++ b/crates/nu-command/src/math/tan.rs @@ -0,0 +1,105 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math tan" + } + + fn signature(&self) -> Signature { + Signature::build("math tan") + .switch("degrees", "Use degrees instead of radians", Some('d')) + .input_output_types(vec![(Type::Number, Type::Float)]) + .vectorizes_over_list(true) + .category(Category::Math) + } + + fn usage(&self) -> &str { + "Returns the tangent of the number." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["trigonometry"] + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let use_degrees = call.has_flag("degrees"); + input.map( + move |value| operate(value, head, use_degrees), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Apply the tangent to pi/4", + example: "(math pi) / 4 | math tan", + result: Some(Value::test_float(1f64)), + }, + Example { + description: "Apply the tangent to a list of angles in degrees", + example: "[-45 0 45] | math tan -d", + result: Some(Value::List { + vals: vec![ + Value::test_float(-1f64), + Value::test_float(0f64), + Value::test_float(1f64), + ], + span: Span::test_data(), + }), + }, + ] + } +} + +fn operate(value: Value, head: Span, use_degrees: bool) -> Value { + match value { + numeric @ (Value::Int { .. } | Value::Float { .. }) => { + let (val, span) = match numeric { + Value::Int { val, span } => (val as f64, span), + Value::Float { val, span } => (val, span), + _ => unreachable!(), + }; + + let val = if use_degrees { val.to_radians() } else { val }; + + Value::Float { + val: val.tan(), + span, + } + } + other => Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Only numerical values are supported, input type: {:?}", + other.get_type() + ), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +}