From 480d48351cfa52e897fed91da77ed7c10d96cc76 Mon Sep 17 00:00:00 2001 From: Looouiiis Date: Thu, 30 May 2024 12:15:02 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(math):=20add=20round=20options?= =?UTF-8?q?=20(#9117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add round options, but I think can also add floor, ceiling, etc. And the default mode is trunc. Closes #9117 Co-authored-by: Mahmoud Al-Qudsi --- CHANGELOG.rst | 1 + doc_src/cmds/math.rst | 12 ++++++--- src/builtins/math.rs | 59 ++++++++++++++++++++++++++++++++++++++---- tests/checks/math.fish | 38 +++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 06dc9cf60..f8fd1087e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -126,6 +126,7 @@ Scripting improvements - Commas in command substitution output are no longer used as separators in brace expansion, preventing a surprising expansion in rare cases (:issue:`5048`). - Universal variables can now store strings containing invalid Unicode codepoints (:issue:`10313`). - ``path basename`` now takes a ``-E`` option that causes it to return the basename (i.e. "filename" with the directory prefix removed) with the final extension (if any) also removed. This takes the place of ``path change-extension "" (path basename $foo)`` (:issue:`10521`). +- ``math`` now adds ``--scale-mode`` parameter. You can choose between ``truncate``, ``round``, ``floor``, ``ceiling`` as you wish (default value is ``truncate``). (:issue:`9117`). Interactive improvements ------------------------ diff --git a/doc_src/cmds/math.rst b/doc_src/cmds/math.rst index 3dd731691..2c39b3228 100644 --- a/doc_src/cmds/math.rst +++ b/doc_src/cmds/math.rst @@ -8,7 +8,7 @@ Synopsis .. synopsis:: - math [(-s | --scale) N] [(-b | --base) BASE] EXPRESSION ... + math [(-s | --scale) N] [(-b | --base) BASE] [(-m | --scale-mode) MODE] EXPRESSION ... Description @@ -19,7 +19,6 @@ It supports simple operations such as addition, subtraction, and so on, as well By default, the output shows up to 6 decimal places. To change the number of decimal places, use the ``--scale`` option, including ``--scale=0`` for integer output. -Trailing zeroes will always be trimmed. Keep in mind that parameter expansion happens before expressions are evaluated. This can be very useful in order to perform calculations involving shell variables or the output of command substitutions, but it also means that parenthesis (``()``) and the asterisk (``*``) glob character have to be escaped or quoted. @@ -37,8 +36,8 @@ The following options are available: **-s** *N* or **--scale** *N* Sets the scale of the result. ``N`` must be an integer or the word "max" for the maximum scale. - A scale of zero causes results to be truncated, not rounded. Any non-integer component is thrown away. - So ``3/2`` returns ``1`` rather than ``2`` which ``1.5`` would normally round to. + A scale of zero causes results to be truncated by default. Any non-integer component is thrown away. + So ``3/2`` returns ``1`` by default, rather than ``2`` which ``1.5`` would normally round to. This is for compatibility with ``bc`` which was the basis for this command prior to fish 3.0.0. Scale values greater than zero causes the result to be rounded using the usual rules to the specified number of decimal places. @@ -49,6 +48,11 @@ The following options are available: Hex numbers will be printed with a ``0x`` prefix. Octal numbers will have a prefix of ``0`` but aren't understood by ``math`` as input. +**-m** *MODE* or **--scale-mode** *MODE* + Sets scale behavior. + The ``MODE`` can be ``truncate``, ``round``, ``floor``, ``ceiling``. + The default value of scale mode is ``round`` with non zero scale and ``truncate`` with zero scale. + **-h** or **--help** Displays help about using this command. diff --git a/src/builtins/math.rs b/src/builtins/math.rs index 8f08bf683..2de0b1738 100644 --- a/src/builtins/math.rs +++ b/src/builtins/math.rs @@ -1,17 +1,31 @@ +use num_traits::pow; +use widestring::utf32str; + use super::prelude::*; use crate::tinyexpr::te_interp; /// The maximum number of points after the decimal that we'll print. const DEFAULT_SCALE: usize = 6; +const DEFAULT_ZERO_SCALE_MODE: ZeroScaleMode = ZeroScaleMode::Default; + /// The end of the range such that every integer is representable as a double. /// i.e. this is the first value such that x + 1 == x (or == x + 2, depending on rounding mode). const MAX_CONTIGUOUS_INTEGER: f64 = (1_u64 << f64::MANTISSA_DIGITS) as f64; +enum ZeroScaleMode { + Truncate, + Round, + Floor, + Ceiling, + Default, +} + struct Options { print_help: bool, scale: usize, base: usize, + zero_scale_mode: ZeroScaleMode, } fn parse_cmd_opts( @@ -24,17 +38,19 @@ fn parse_cmd_opts( // This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing. // This is needed because of the minus, `-`, operator in math expressions. - const SHORT_OPTS: &wstr = L!("+:hs:b:"); + const SHORT_OPTS: &wstr = L!("+:hs:b:m:"); const LONG_OPTS: &[WOption] = &[ wopt(L!("scale"), ArgType::RequiredArgument, 's'), wopt(L!("base"), ArgType::RequiredArgument, 'b'), wopt(L!("help"), ArgType::NoArgument, 'h'), + wopt(L!("scale-mode"), ArgType::RequiredArgument, 'm'), ]; let mut opts = Options { print_help: false, scale: DEFAULT_SCALE, base: 10, + zero_scale_mode: DEFAULT_ZERO_SCALE_MODE, }; let mut have_scale = false; @@ -62,6 +78,23 @@ fn parse_cmd_opts( opts.scale = scale as usize; } } + 'm' => { + let optarg = w.woptarg.unwrap(); + if optarg.eq(utf32str!("truncate")) { + opts.zero_scale_mode = ZeroScaleMode::Truncate; + } else if optarg.eq(utf32str!("round")) { + opts.zero_scale_mode = ZeroScaleMode::Round; + } else if optarg.eq(utf32str!("floor")) { + opts.zero_scale_mode = ZeroScaleMode::Floor; + } else if optarg.eq(utf32str!("ceiling")) { + opts.zero_scale_mode = ZeroScaleMode::Ceiling; + } else { + streams + .err + .append(wgettext_fmt!("%ls: %ls: invalid mode\n", cmd, optarg)); + return Err(STATUS_INVALID_ARGS); + } + } 'b' => { let optarg = w.woptarg.unwrap(); if optarg == "hex" { @@ -129,10 +162,26 @@ fn format_double(mut v: f64, opts: &Options) -> WString { return sprintf!("%s0%lo", mneg, v.abs() as u64); } - // As a special-case, a scale of 0 means to truncate to an integer - // instead of rounding. - if opts.scale == 0 { - v = v.trunc(); + v *= pow(10f64, opts.scale); + + v = match opts.zero_scale_mode { + ZeroScaleMode::Truncate => v.trunc(), + ZeroScaleMode::Round => v.round(), + ZeroScaleMode::Floor => v.floor(), + ZeroScaleMode::Ceiling => v.ceil(), + ZeroScaleMode::Default => { + if opts.scale == 0 { + v.trunc() + } else { + v + } + } + }; + + // if we don't add check here, the result of 'math -s 0 "22 / 5 - 5"' will be '0', not '-0' + if opts.scale != 0 { + v /= pow(10f64, opts.scale); + } else { return sprintf!("%.*f", opts.scale, v); } diff --git a/tests/checks/math.fish b/tests/checks/math.fish index 9c6f8e34e..712160c5c 100644 --- a/tests/checks/math.fish +++ b/tests/checks/math.fish @@ -379,3 +379,41 @@ math 0x0_2.0P-f # CHECKERR: math: Error: Unexpected token # CHECKERR: '0x0_2.0P-f' # CHECKERR: ^ +math "22 / 5 - 5" +# CHECK: -0.6 +math -s 0 --scale-mode=truncate "22 / 5 - 5" +# CHECK: -0 +math --scale=0 -m truncate "22 / 5 - 5" +# CHECK: -0 +math -s 0 --scale-mode=floor "22 / 5 - 5" +# CHECK: -1 +math -s 0 --scale-mode=round "22 / 5 - 5" +# CHECK: -1 +math -s 0 --scale-mode=ceiling "22 / 5 - 5" +# CHECK: -0 +math "1 / 3 - 1" +# CHECK: -0.666667 +math --scale-mode=truncate "1 / 3 - 1" +# CHECK: -0.666666 +math --scale-mode=floor "1 / 3 - 1" +# CHECK: {{-0.666667|-0.666668}} +math --scale-mode=floor "2 / 3 - 1" +# CHECK: {{-0.333334|-0.333335}} +math --scale-mode=round "1 / 3 - 1" +# CHECK: {{-0.666667|-0.666668}} +math --scale-mode=ceiling "1 / 3 - 1" +# CHECK: -0.666666 +math --scale-mode=ceiling "2 / 3 - 1" +# CHECK: -0.333333 +math -s 6 --scale-mode=truncate "1 / 3 - 1" +# CHECK: -0.666666 +math -s 6 --scale-mode=floor "1 / 3 - 1" +# CHECK: {{-0.666667|-0.666668}} +math -s 6 --scale-mode=floor "2 / 3 - 1" +# CHECK: {{-0.333334|-0.333335}} +math -s 6 --scale-mode=round "1 / 3 - 1" +# CHECK: {{-0.666667|-0.666668}} +math -s 6 --scale-mode=ceiling "1 / 3 - 1" +# CHECK: -0.666666 +math -s 6 --scale-mode=ceiling "2 / 3 - 1" +# CHECK: -0.333333