diff --git a/doc_src/cmds/math.rst b/doc_src/cmds/math.rst index 8669b2cbe..84f236414 100644 --- a/doc_src/cmds/math.rst +++ b/doc_src/cmds/math.rst @@ -8,7 +8,7 @@ Synopsis :: - math [-sN | --scale=N] [--] EXPRESSION + math [-sN | --scale=N] [-bBASE | --base=BASE] [--] EXPRESSION Description @@ -26,6 +26,8 @@ The following options are available: - ``-sN`` 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 rounded down to the nearest integer. So ``3/2`` returns ``1`` 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. +- ``-b BASE`` or ``--base BASE`` sets the numeric base used for output (``math`` always understands hexadecimal numbers as input). It currently understands "hex" or "16" for hexadecimal and "octal" or "8" for octal and implies a scale of 0 (other scales cause an error), so it will truncate the result down to an integer. This might change in the future. Hex numbers will be printed with a ``0x`` prefix. Octal numbers will have a prefix of ``0`` and aren't understood by ``math`` as input. + Return Values ------------- @@ -119,6 +121,8 @@ Examples ``math "bitor(9,2)"`` outputs 11. +``math --base=hex 192`` prints ``0xc0``. + Compatibility notes ------------------- diff --git a/src/builtin_math.cpp b/src/builtin_math.cpp index 84111fb39..2bf6514d1 100644 --- a/src/builtin_math.cpp +++ b/src/builtin_math.cpp @@ -29,13 +29,16 @@ static constexpr double kMaximumContiguousInteger = struct math_cmd_opts_t { bool print_help = false; + bool have_scale = false; int scale = kDefaultScale; + int base = 10; }; // This command is atypical in using the "+" (REQUIRE_ORDER) option for flag parsing. // This is needed because of the minus, `-`, operator in math expressions. -static const wchar_t *const short_options = L"+:hs:"; +static const wchar_t *const short_options = L"+:hs:b:"; static const struct woption long_options[] = {{L"scale", required_argument, nullptr, 's'}, + {L"base", required_argument, nullptr, 'b'}, {L"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}}; @@ -47,6 +50,7 @@ static int parse_cmd_opts(math_cmd_opts_t &opts, int *optind, //!OCLINT(high nc while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { switch (opt) { case 's': { + opts.have_scale = true; // "max" is the special value that tells us to pick the maximum scale. if (std::wcscmp(w.woptarg, L"max") == 0) { opts.scale = 15; @@ -60,6 +64,21 @@ static int parse_cmd_opts(math_cmd_opts_t &opts, int *optind, //!OCLINT(high nc } break; } + case 'b': { + if (std::wcscmp(w.woptarg, L"hex") == 0) { + opts.base = 16; + } else if (std::wcscmp(w.woptarg, L"octal") == 0) { + opts.base = 8; + } else { + opts.base = fish_wcstoi(w.woptarg); + if (errno || (opts.base != 8 && opts.base != 16)) { + streams.err.append_format(_(L"%ls: '%ls' is not a valid base value\n"), + cmd, w.woptarg); + return STATUS_INVALID_ARGS; + } + } + break; + } case 'h': { opts.print_help = true; break; @@ -79,6 +98,11 @@ static int parse_cmd_opts(math_cmd_opts_t &opts, int *optind, //!OCLINT(high nc } } } + if (opts.have_scale && opts.scale != 0 && opts.base != 10) { + streams.err.append_format(_(L"%ls: Bases other than 10 can only do scale=0 output currently\n"), + cmd, w.woptarg); + return STATUS_INVALID_ARGS; + } *optind = w.woptind; return STATUS_CMD_OK; @@ -158,6 +182,14 @@ static const wchar_t *math_describe_error(const te_error_t &error) { /// Return a formatted version of the value \p v respecting the given \p opts. static wcstring format_double(double v, const math_cmd_opts_t &opts) { + if (opts.base == 16) { + v = trunc(v); + return format_string(L"0x%x", (long)v); + } else if (opts.base == 8) { + v = trunc(v); + return format_string(L"0%o", (long)v); + } + // As a special-case, a scale of 0 means to truncate to an integer // instead of rounding. if (opts.scale == 0) { @@ -165,6 +197,7 @@ static wcstring format_double(double v, const math_cmd_opts_t &opts) { return format_string(L"%.*f", opts.scale, v); } + wcstring ret = format_string(L"%.*f", opts.scale, v); // If we contain a decimal separator, trim trailing zeros after it, and then the separator // itself if there's nothing after it. Detect a decimal separator as a non-digit. diff --git a/tests/checks/math.fish b/tests/checks/math.fish index d5179f834..26746717f 100644 --- a/tests/checks/math.fish +++ b/tests/checks/math.fish @@ -173,3 +173,16 @@ math 'log(16' # CHECKERR: math: Error: Missing closing parenthesis # CHECKERR: 'log(16' # CHECKERR: ^ + +math --base=16 255 / 15 +# CHECK: 0x11 +math -bhex 16 x 2 +# CHECK: 0x20 +math --base hex 12 + 0x50 +# CHECK: 0x5c +math --base octal --scale=0 55 +# CHECK: 067 +math --base notabase +# CHECKERR: math: 'notabase' is not a valid base value +echo $status +# CHECK: 2