math: Add --base option

Currently a bit limited, unfortunately printf's `%a` specifier is
absolutely unreadable.

So we add `hex` and `octal` with `0x` and `0` prefixes respectively,
and also take a number but currently only allow 16 and 8.

The output is truncated to integer, so scale values other than 0 are
invalid and 0 is implied.

The docs mention this may change.
This commit is contained in:
Fabian Homborg 2020-10-07 19:03:19 +02:00
parent 30c7a17302
commit 5872f4522d
3 changed files with 52 additions and 2 deletions

View file

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

View file

@ -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.

View file

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