NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: lib/59811: strpct round-off error
The following reply was made to PR lib/59811; it has been noted by GNATS.
From: Robert Elz <kre%munnari.OZ.AU@localhost>
To: gnats-bugs%netbsd.org@localhost
Cc:
Subject: Re: lib/59811: strpct round-off error
Date: Wed, 03 Dec 2025 12:12:13 +0700
I now have (what I believe to be) working code to fix this.
I am including 2 potential manual pages - please tell me which one
you prefer (as in, which style of the fix -- in the actual text, I
worked on the 2nd version below after the first, and in the process
improved its wording - I think - if the first one is the preferred
method, I'll redo the wording of it to be more like the second).
I also "enhanced" slightly the OP's mwe.c test program, to use in testing
this - mwe2 takes args, with options for the rounding mode desired and the
number of fractional digits (also the buffer size, but that's just for
testing perverse stupid usages), its usage is:
Usage: mwe2 [-f digits] [-r mode] [-s bufsize] [ numerator [ denominator ] ]
And using that with the original numerator & denominator (as given in this
PR, which remain the defaults) and with -f1 (to get one decimal place),
with the 3 rounding modes, I see:
jacaranda$ ./mwe2 -f1 -r0
Current: 66.6
Expected: 66.6
jacaranda$ ./mwe2 -f1 -r1
Current: 66.7
Expected: 66.7
jacaranda$ ./mwe2 -f1 -r2
Current: 66.7
Expected: 66.7
Note that the "expected" value is now computed (in mwe2.c, using floating
point) to be consistent with the -f and -r args given.
In case it happens not to be obvious by its very nature, -r1 always
produces the same output as either -r0 or -r2 (which depends upon the
data).
Then also:
jacaranda$ ./mwe2 -f2 -r1
Current: 66.67
Expected: 66.67
jacaranda$ ./mwe2 -f2 -r0
Current: 66.66
Expected: 66.66
jacaranda$ ./mwe2 -f12 -r1
Current: 66.666666682064
Expected: 66.666666682064
jacaranda$ ./mwe2 -f11 -r1
Current: 66.66666668206
Expected: 66.66666668206
jacaranda$ ./mwe2 -f11 -r2
Current: 66.66666668207
Expected: 66.66666668207
The current implementation uses the first of the man pages below, but
changing it to be the second will take only about a minute, so ignore that.
I believe these do need a method to specify which rounding style to use,
how to do that is the difference between the two man pages below. It would
be easy to just not do that, and simply pick one of them (and since df's
usage is fairly simple, it could adjust the args to force the rounding it
is supposed to use, if the one and only style implemented were not the one
that it requires.)
These also presume that any changes implemented now will get pulled up
to NetBSD 11 - that is by no means certain. If it doesn't happen, the
reference to .Nx 11.0 can easily turn into .Nx 12.0.
I have tried to avoid inventing names for anything, as much as is possible,
so please feel free to make suggestions for unnamed objects, or alterations
where I invented one.
So, here are the two possible manual pages (sorry, no visible markup here,
which makes it harder to read, I know - it exists though):
STRPCT(3) Library Functions Manual STRPCT(3)
NAME
strpct, strspct - decimal percent formatters
LIBRARY
System Utilities Library (libutil, -lutil)
SYNOPSIS
#include <util.h>
char *
strpct(char *buf, size_t bufsiz, uintmax_t numerator,
uintmax_t denominator, size_t precision);
char *
strspct(char *buf, size_t bufsiz, intmax_t numerator,
intmax_t denominator, size_t precision);
int
strpct_round(int mode);
DESCRIPTION
The strpct() function formats the fraction represented by numerator and
denominator into a percentage representation with given number of digits
of precision without using floating point arithmetic.
The strspct() function is identical to strpct() except uses signed values
for the numerator and denominator, and so can return a result with a
leading minus sign.
The result is rounded according to the current rounding mode, which for
historical reasons defaults to rounding towards zero (that is truncating,
as would be done using integer arithmetic). This can be altered using
strpct_round(). The mode specifies which of 3 implemented rounding modes
to use. If mode is given as zero, the default of rounding towards 0 (no
rounding) is used. If mode is given as 1, the least significant digit of
the result returned will be rounded to the nearest correct value. Values
mid way between one digit and the next round away from zero. If mode is
given as 2, the least significant digit of the result will be incremented
if there were any less significant non-zero digits not included in the
result -- that is, rounded away from zero. If mode is given as INT_MAX
or -INT_MAX, the rounding will be set as for mode 0. Those values are
defined to allow for future implementation (unlikely to ever happen) of
routing towards infinity (INT_MAX) or negative infinity (-INT_MAX). Any
other value for mode is simply igored, and the rounding mode is not
changed.
RETURN VALUES
strpct() and strspct() always return a pointer to a NUL-terminated
(unless bufsiz is 0) formatted string which is placed in buf.
strpct_round() returns the rounding mode that was in use before the call.
To obtain this information without altering the rounding mode, call
strpct_round() with a mode of -1.
EXAMPLES
strpct(buf, sizeof(buf), 1, 16, 3);
=> "6.250"
strpct(buf, sizeof(buf), 1, 2, 0);
=> "50"
HISTORY
strpct() was originally implemented in csh(1) for NetBSD 1.3. It printed
into a static buffer, was not locale aware, handled unsigned long
numbers, and printed a "%" at the end of the number. Other programs such
as df(1) and time(1) started using it. strpct() and strspct() appeared
separately in libutil for NetBSD 6.0.
strpct_round() appeared in NetBSD 11.0.
AUTHORS
Erik E. Fair <fair%NetBSD.org@localhost>
Roland Illig <rillig%NetBSD.org@localhost>
NetBSD 11.99.4 December 3, 2025 NetBSD 11.99.4
or, an alternative:
STRPCT(3) Library Functions Manual STRPCT(3)
NAME
strpct, strspct, strpct_r, strspct_r - decimal percent formatters
LIBRARY
System Utilities Library (libutil, -lutil)
SYNOPSIS
#include <util.h>
char *
strpct(char *buf, size_t bufsiz, uintmax_t numerator,
uintmax_t denominator, size_t precision);
char *
strpct_r(char *buf, size_t bufsiz, uintmax_t numerator,
uintmax_t denominator, size_t precision, int rounding_mode);
char *
strspct(char *buf, size_t bufsiz, intmax_t numerator,
intmax_t denominator, size_t precision);
char *
strspct_r(char *buf, size_t bufsiz, intmax_t numerator,
intmax_t denominator, size_t precision, int rounding_mode);
DESCRIPTION
The strpct_r() function formats the fraction represented by numerator and
denominator into a percentage representation with given number of digits
of precision without using floating point arithmetic.
The result is stored in the buf in which a maximum of bufsiz - 1
meaningful bytes can be stored, and is rounded according to the
rounding_mode. The current locale's radix character, typically period
(`.') or comma (`,'), is inserted before fractional digits if the
precision is greater than 0. If rounding_mode is given as zero, rounding
towards 0 (truncating rather than rounding) is used. If rounding_mode is
given as 1, the result returned will be rounded to the nearest correct
value. Actual values exactly mid way between one representable value and
the next round away from zero. If rounding_mode is given as 2, the
result will be rounded, if needed, to the next value further away from
zero.
The strspct_r() function is identical to strpct_r() except uses signed
values for the numerator and denominator, and so can return a result with
a leading minus sign.
The strpct() and strspct() functions are identical to strpct_r() and
strspct_r() respectively, with the rounding_mode specified as zero.
RETURN VALUES
strpct_r(), strspct_r(), strpct() and strspct() always return a pointer
to a NUL-terminated (unless bufsiz is 0) formatted string which is placed
in buf.
EXAMPLES
strpct(buf, sizeof(buf), 1, 16, 3);
=> "6.250"
strpct(buf, sizeof(buf), 1, 2, 0);
=> "50"
HISTORY
strpct() was originally implemented in csh(1) for NetBSD 1.3. It printed
into a static buffer, was not locale aware, handled unsigned long
numbers, and printed a "%" at the end of the number. Other programs such
as df(1) and time(1) started using it. strpct() and strspct() appeared
separately in libutil for NetBSD 6.0.
strpct_r() and strspct_r() appeared in NetBSD 11.0.
AUTHORS
Erik E. Fair <fair%NetBSD.org@localhost>
Roland Illig <rillig%NetBSD.org@localhost>
BUGS
If the supplied buffer size is insufficient for the result (including a
terminating nul (`\0') character), the result will be silently truncated
with only the most significant digits included, the last of which will be
rounded using the requested method. This is not useful. If the buffer
space is exhausted part way through a locale's (multi-byte) radix
character, even more bizarre behaviour is to be expected. Always provide
a buffer bigger than can possibly be needed.
Rather than causing an abnormal process termination, as it arguably
should, a denominator specified as zero will be treated as if it were
one.
NetBSD 11.99.4 December 3, 2025 NetBSD 11.99.4
Home |
Main Index |
Thread Index |
Old Index