embiggen notcurses_metric(3), add unit test #1107

This commit is contained in:
nick black 2020-11-28 17:08:45 -05:00 committed by Nick Black
parent 9135663529
commit dd8423dff3
3 changed files with 101 additions and 35 deletions

View File

@ -27,6 +27,8 @@ notcurses_metric - fixed-width numeric output with metric suffixes
**static inline const char* qprefix(uintmax_t ***val***, uintmax_t ***decimal***, char* ***buf***, int ***omitdec***);**
**static inline const char* iprefix(uintmax_t ***val***, uintmax_t ***decimal***, char* ***buf***, int ***omitdec***);**
**static inline const char* bprefix(uintmax_t ***val***, uintmax_t ***decimal***, char* ***buf***, int ***omitdec***);**
# DESCRIPTION
@ -39,8 +41,8 @@ amounts of growth, but is designed for 1000 (**PREFIX**) or 1024
and gibibits. **ncmetric** supports the large suffixes KMGTPEZY (Kilo, Mega,
Giga, Tera, Peta, Exa, Zetta, and Yotta) and the small suffixes mµnpfazy
(Milli, Micro, Nano, Pico, Femto, Atto, Zepto, and Yocto). This covers the
range 10^-24 through 10^24. As **uintmax_t** is typically only 64 bits, this
covers the entirety of its range.
range 1e24 (one septillion) through 1e-24, sufficing for all possible values of
a 64-bit **uintmax_t**.
**val** is the value being output, having been scaled by **decimal**.
**decimal** will typically be 1; to represent values less than 1, **decimal**
@ -51,31 +53,91 @@ must be at least:
* **IPREFIXSTRLEN** + 1 bytes for a 1024-based value
* **BPREFIXSTRLEN** + 1 bytes for a 1024-based value with an 'i' suffix
Three helper functions are provided to simplify these common cases:
```
// Mega, kilo, gigafoo. Use PREFIXSTRLEN + 1 and PREFIXCOLUMNS.
static inline const char*
qprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1000, '\0');
}
// Mibi, kebi, gibibytes sans 'i' suffix. Use IPREFIXSTRLEN + 1.
static inline const char*
iprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, '\0');
}
// Mibi, kebi, gibibytes. Use BPREFIXSTRLEN + 1 and BPREFIXCOLUMNS.
static inline const char*
bprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, 'i');
}
```
If **omitdec** is not zero, the decimal point and mantissa will be
omitted if all digits to be displayed would be zero. The decimal point takes
the current locale into account (see **setlocale(3)** and **localeconv(3)**).
**mult** is the relative multiple for each suffix. **uprefix**, if not zero,
will be used as a suffix following any metric suffix.
In general, the maximum-width output will take the form:
The maximum number of columns is not directly related to the maximum number of
bytes, since Unicode doesn't necessarily map to single-byte characters
(including the 'µ' micro suffix). The corresponding defines for maximum column
length are:
CCC.mmMu
* **PREFIXCOLUMNS** (7)
* **IPREFIXCOLUMNS** (8)
* **BPREFIXCOLUMNS** (9)
Where C are digits of the characteristic (up to ceil(log10(**mult**)) digits),
the decimal point follows, m are digits of the mantissa (up to 2), M is the
metric suffix, and u is the **uprefix**. The minimum-width output will take
the form:
In general, the maximum-width output will take the form **CCC.mmMu**, where C
are digits of the characteristic (up to ceil(log10(**mult**)) digits), the
decimal point follows, m are digits of the mantissa (up to 2), M is the metric
suffix, and u is the **uprefix**. The minimum-width output will take the form
**C**. This minimal form can occur if **omitdec** is non-zero and a
single-column value such as 5 is passed for **val**.
C
Three more defines are provided to simplify formatted fixed-width output using
the results of **ncmetric**. Each of these macros accepts a character buffer
holding the result of the call, and expand to *two* arguments:
This can occur if **omitdec** is non-zero and a value such as 5 is passed
for **val**.
* **PREFIXFMT(x)**
* **IPREFIXFMT(x)**
* **BPREFIXFMT(x)**
These can be used in e.g. the following ungainly fashion:
**ncplane_printf(n, "%*s", PREFIXFMT(buf));**
to ensure that the output is always **PREFIXCOLUMNS** wide.
# RETURN VALUES
**NULL** if input parameters were invalid. Otherwise, a pointer to **buf**,
filled in with the formatted output.
# EXAMPLES
**ncmetric(0, 1, buf, 0, 1000, '\0')**: "0.00".
**ncmetric(0, 1, buf, 1, 1000, '\0')**: "0".
**ncmetric(1023, 1, buf, 1, 1000, '\0')**: "1.02K".
**ncmetric(1023, 1, buf, 1, 1024, 'i')**: "1023".
**ncmetric(1024, 1, buf, 1, 1024, 'i')**: "1Ki".
**ncmetric(4096, 1, buf, 0, 1024, 'i')**: "4.00Ki".
**ncmetric(0x7fffffffffffffff, 1, buf, 0, 1000, '\0')**: "9.22E".
**ncmetric(0xffffffffffffffff, 1, buf, 0, 1000, '\0')**: "18.45E".
# BUGS
This function is difficult to understand.
# SEE ALSO
**localeconv(3)**,

View File

@ -2608,6 +2608,30 @@ API struct ncplane* nctablet_plane(struct nctablet* t);
API struct ncplane* nctablet_ncplane(struct nctablet* t)
__attribute__ ((deprecated));
// Takes an arbitrarily large number, and prints it into a fixed-size buffer by
// adding the necessary SI suffix. Usually, pass a |[IB]PREFIXSTRLEN+1|-sized
// buffer to generate up to |[IB]PREFIXCOLUMNS| columns' worth of EGCs. The
// characteristic can occupy up through |mult-1| characters (3 for 1000, 4 for
// 1024). The mantissa can occupy either zero or two characters.
//
// Floating-point is never used, because an IEEE758 double can only losslessly
// represent integers through 2^53-1.
//
// 2^64-1 is 18446744073709551615, 18.45E(xa). KMGTPEZY thus suffice to handle
// an 89-bit uintmax_t. Beyond Z(etta) and Y(otta) lie lands unspecified by SI.
// 2^-63 is 0.000000000000000000108, 1.08a(tto).
// val: value to print
// decimal: scaling. '1' if none has taken place.
// buf: buffer in which string will be generated
// omitdec: inhibit printing of all-0 decimal portions
// mult: base of suffix system (almost always 1000 or 1024)
// uprefix: character to print following suffix ('i' for kibibytes basically).
// only printed if suffix is actually printed (input >= mult).
//
// You are encouraged to consult notcurses_metric(3).
API const char* ncmetric(uintmax_t val, uintmax_t decimal, char* buf,
int omitdec, uintmax_t mult, int uprefix);
// The number of columns is one fewer, as the STRLEN expressions must leave
// an extra byte open in case 'µ' (U+00B5, 0xC2 0xB5) shows up. PREFIXCOLUMNS
// is the maximum number of columns used by a mult == 1000 (standard)
@ -2629,29 +2653,7 @@ API struct ncplane* nctablet_ncplane(struct nctablet* t)
#define IPREFIXFMT(x) NCMETRIXFWIDTH((x), IPREFIXCOLUMNS), (x)
#define BPREFIXFMT(x) NCMETRICFWIDTH((x), BPREFIXCOLUMNS), (x)
// Takes an arbitrarily large number, and prints it into a fixed-size buffer by
// adding the necessary SI suffix. Usually, pass a |[IB]PREFIXSTRLEN+1|-sized
// buffer to generate up to |[IB]PREFIXCOLUMNS| columns' worth of EGCs. The
// characteristic can occupy up through |mult-1| characters (3 for 1000, 4 for
// 1024). The mantissa can occupy either zero or two characters.
//
// Floating-point is never used, because an IEEE758 double can only losslessly
// represent integers through 2^53-1.
//
// 2^64-1 is 18446744073709551615, 18.45E(xa). KMGTPEZY thus suffice to handle
// an 89-bit uintmax_t. Beyond Z(etta) and Y(otta) lie lands unspecified by SI.
// 2^-63 is 0.000000000000000000108, 1.08a(tto).
// val: value to print
// decimal: scaling. '1' if none has taken place.
// buf: buffer in which string will be generated
// omitdec: inhibit printing of all-0 decimal portions
// mult: base of suffix system (almost always 1000 or 1024)
// uprefix: character to print following suffix ('i' for kibibytes basically).
// only printed if suffix is actually printed (input >= mult).
API const char* ncmetric(uintmax_t val, uintmax_t decimal, char* buf,
int omitdec, uintmax_t mult, int uprefix);
// Mega, kilo, gigafoo. Use PREFIXSTRLEN + 1.
// Mega, kilo, gigafoo. Use PREFIXSTRLEN + 1 and PREFIXCOLUMNS.
static inline const char*
qprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1000, '\0');
@ -2663,7 +2665,7 @@ iprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, '\0');
}
// Mibi, kebi, gibibytes. Use BPREFIXSTRLEN + 1.
// Mibi, kebi, gibibytes. Use BPREFIXSTRLEN + 1 and BPREFIXCOLUMNS.
static inline const char*
bprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, 'i');

View File

@ -67,6 +67,8 @@ TEST_CASE("Metric") {
CHECK(!strcmp("1.00Ki", buf));
impericize_ncmetric(4096, 1, buf, 1, 1000, '\0');
CHECK(!strcmp("4.10K", buf));
impericize_ncmetric(4096, 1, buf, 0, 1024, 'i');
CHECK(!strcmp("4.00Ki", buf));
impericize_ncmetric(4096, 1, buf, 1, 1024, 'i');
CHECK(!strcmp("4Ki", buf));
}