diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index e29c6c3082..6f4682d538 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -223,6 +223,9 @@ pub fn create_default_context(interactive: bool) -> Result &str { + "math ceil" + } + + fn signature(&self) -> Signature { + Signature::build("math celi") + } + + fn usage(&self) -> &str { + "Applies the ceil function to a list of numbers" + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + run_with_numerical_functions_on_stream( + RunnableContext { + input: args.input, + registry: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + ctrl_c: args.ctrl_c, + current_errors: args.current_errors, + name: args.call_info.name_tag, + raw_input: args.raw_input, + }, + ceil_big_int, + ceil_big_decimal, + ceil_default, + ) + .await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Apply the ceil function to a list of numbers", + example: "echo [1.5 2.3 -3.1] | math ceil", + result: Some(vec![ + UntaggedValue::int(2).into(), + UntaggedValue::int(3).into(), + UntaggedValue::int(-3).into(), + ]), + }] + } +} + +fn ceil_big_int(val: BigInt) -> Value { + UntaggedValue::int(val).into() +} + +fn ceil_big_decimal(val: BigDecimal) -> Value { + let mut maybe_ceiled = val.round(0); + if maybe_ceiled < val { + maybe_ceiled += BigDecimal::one(); + } + let (ceiled, _) = maybe_ceiled.into_bigint_and_exponent(); + UntaggedValue::int(ceiled).into() +} + +fn ceil_default(_: UntaggedValue) -> Value { + UntaggedValue::Error(ShellError::unexpected( + "Only numerical values are supported", + )) + .into() +} + +#[cfg(test)] +mod tests { + use super::ShellError; + use super::SubCommand; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + Ok(test_examples(SubCommand {})?) + } +} diff --git a/crates/nu-cli/src/commands/math/floor.rs b/crates/nu-cli/src/commands/math/floor.rs new file mode 100644 index 0000000000..90b5aff24a --- /dev/null +++ b/crates/nu-cli/src/commands/math/floor.rs @@ -0,0 +1,91 @@ +use crate::commands::math::utils::run_with_numerical_functions_on_stream; +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use bigdecimal::One; +use nu_errors::ShellError; +use nu_protocol::{Signature, UntaggedValue, Value}; + +pub struct SubCommand; + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "math floor" + } + + fn signature(&self) -> Signature { + Signature::build("math floor") + } + + fn usage(&self) -> &str { + "Applies the floor function to a list of numbers" + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + run_with_numerical_functions_on_stream( + RunnableContext { + input: args.input, + registry: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + ctrl_c: args.ctrl_c, + current_errors: args.current_errors, + name: args.call_info.name_tag, + raw_input: args.raw_input, + }, + floor_big_int, + floor_big_decimal, + floor_default, + ) + .await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Apply the floor function to a list of numbers", + example: "echo [1.5 2.3 -3.1] | math floor", + result: Some(vec![ + UntaggedValue::int(1).into(), + UntaggedValue::int(2).into(), + UntaggedValue::int(-4).into(), + ]), + }] + } +} + +fn floor_big_int(val: BigInt) -> Value { + UntaggedValue::int(val).into() +} + +fn floor_big_decimal(val: BigDecimal) -> Value { + let mut maybe_floored = val.round(0); + if maybe_floored > val { + maybe_floored -= BigDecimal::one(); + } + let (floored, _) = maybe_floored.into_bigint_and_exponent(); + UntaggedValue::int(floored).into() +} + +fn floor_default(_: UntaggedValue) -> Value { + UntaggedValue::Error(ShellError::unexpected( + "Only numerical values are supported", + )) + .into() +} + +#[cfg(test)] +mod tests { + use super::ShellError; + use super::SubCommand; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + Ok(test_examples(SubCommand {})?) + } +} diff --git a/crates/nu-cli/src/commands/math/mod.rs b/crates/nu-cli/src/commands/math/mod.rs index 563df92f6d..eca8ac55ce 100644 --- a/crates/nu-cli/src/commands/math/mod.rs +++ b/crates/nu-cli/src/commands/math/mod.rs @@ -1,11 +1,14 @@ pub mod avg; +pub mod ceil; pub mod command; pub mod eval; +pub mod floor; pub mod max; pub mod median; pub mod min; pub mod mode; pub mod product; +pub mod round; pub mod stddev; pub mod sum; pub mod variance; @@ -14,13 +17,16 @@ mod reducers; mod utils; pub use avg::SubCommand as MathAverage; +pub use ceil::SubCommand as MathCeil; pub use command::Command as Math; pub use eval::SubCommand as MathEval; +pub use floor::SubCommand as MathFloor; pub use max::SubCommand as MathMaximum; pub use median::SubCommand as MathMedian; pub use min::SubCommand as MathMinimum; pub use mode::SubCommand as MathMode; pub use product::SubCommand as MathProduct; +pub use round::SubCommand as MathRound; pub use stddev::SubCommand as MathStddev; pub use sum::SubCommand as MathSummation; pub use variance::SubCommand as MathVariance; diff --git a/crates/nu-cli/src/commands/math/round.rs b/crates/nu-cli/src/commands/math/round.rs new file mode 100644 index 0000000000..f1a92dcd28 --- /dev/null +++ b/crates/nu-cli/src/commands/math/round.rs @@ -0,0 +1,85 @@ +use crate::commands::math::utils::run_with_numerical_functions_on_stream; +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{Signature, UntaggedValue, Value}; + +pub struct SubCommand; + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "math round" + } + + fn signature(&self) -> Signature { + Signature::build("math round") + } + + fn usage(&self) -> &str { + "Applies the round function to a list of numbers" + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + run_with_numerical_functions_on_stream( + RunnableContext { + input: args.input, + registry: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + ctrl_c: args.ctrl_c, + current_errors: args.current_errors, + name: args.call_info.name_tag, + raw_input: args.raw_input, + }, + round_big_int, + round_big_decimal, + round_default, + ) + .await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Apply the round function to a list of numbers", + example: "echo [1.5 2.3 -3.1] | math round", + result: Some(vec![ + UntaggedValue::int(2).into(), + UntaggedValue::int(2).into(), + UntaggedValue::int(-3).into(), + ]), + }] + } +} + +fn round_big_int(val: BigInt) -> Value { + UntaggedValue::int(val).into() +} + +fn round_big_decimal(val: BigDecimal) -> Value { + let (rounded, _) = val.round(0).as_bigint_and_exponent(); + UntaggedValue::int(rounded).into() +} + +fn round_default(_: UntaggedValue) -> Value { + UntaggedValue::Error(ShellError::unexpected( + "Only numerical values are supported", + )) + .into() +} +#[cfg(test)] +mod tests { + use super::ShellError; + use super::SubCommand; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + Ok(test_examples(SubCommand {})?) + } +} diff --git a/crates/nu-cli/src/commands/math/utils.rs b/crates/nu-cli/src/commands/math/utils.rs index 7c4311fe7c..76ee0e5771 100644 --- a/crates/nu-cli/src/commands/math/utils.rs +++ b/crates/nu-cli/src/commands/math/utils.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use nu_errors::ShellError; -use nu_protocol::{Dictionary, ReturnSuccess, UntaggedValue, Value}; +use nu_protocol::{Dictionary, Primitive, ReturnSuccess, UntaggedValue, Value}; use indexmap::map::IndexMap; @@ -31,6 +31,26 @@ pub async fn run_with_function( } } +pub type IntFunction = fn(val: BigInt) -> Value; + +pub type DecimalFunction = fn(val: BigDecimal) -> Value; + +pub type DefaultFunction = fn(val: UntaggedValue) -> Value; + +pub async fn run_with_numerical_functions_on_stream( + RunnableContext { input, .. }: RunnableContext, + int_function: IntFunction, + decimal_function: DecimalFunction, + default_function: DefaultFunction, +) -> Result { + let mapped = input.map(move |val| match val.value { + UntaggedValue::Primitive(Primitive::Int(val)) => int_function(val), + UntaggedValue::Primitive(Primitive::Decimal(val)) => decimal_function(val), + other => default_function(other), + }); + Ok(OutputStream::from_input(mapped)) +} + pub fn calculate(values: &[Value], name: &Tag, mf: MathFunction) -> Result { if values.iter().all(|v| v.is_primitive()) { mf(&values, &name) diff --git a/docs/commands/math.md b/docs/commands/math.md index 11e5e0ce4f..7f25cf1b3d 100644 --- a/docs/commands/math.md +++ b/docs/commands/math.md @@ -4,11 +4,14 @@ Mathematical functions that generally only operate on a list of numbers (integer Currently the following functions are implemented: * `math avg`: Finds the average of a list of numbers or tables +* `math ceil`: Applies the ceil function to a list of numbers * [`math eval`](math-eval.md): Evaluates a list of math expressions into numbers +* `math floor`: Applies the floor function to a list of numbers * `math max`: Finds the maximum within a list of numbers or tables * `math median`: Finds the median of a list of numbers or tables * `math min`: Finds the minimum within a list of numbers or tables * `math mode`: Finds the most frequent element(s) within a list of numbers or tables +* `math round`: Applies the round function to a list of numbers * `math stddev`: Finds the standard deviation of a list of numbers or tables * `math sum`: Finds the sum of a list of numbers or tables * `math product`: Finds the product of a list of numbers or tables @@ -117,6 +120,33 @@ To get the average of the file sizes in a directory, simply pipe the size column 328.96 ``` +```shell +> echo [1.5 2.3 -3.1] | math ceil +───┬──── + 0 │ 2 + 1 │ 3 + 2 │ -3 +───┴──── +``` + +```shell +> echo [1.5 2.3 -3.1] | math floor +───┬──── + 0 │ 1 + 1 │ 2 + 2 │ -4 +───┴──── +``` + +```shell +> echo [1.5 2.3 -3.1] | math round +───┬──── + 0 │ 2 + 1 │ 2 + 2 │ -3 +───┴──── +``` + ### Dates ```shell