git: 9dd78db9c30a - main - libc: prevent incorrect %a/%La rounding at full precision
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 14 Sep 2025 04:53:51 UTC
The branch main has been updated by imp:
URL: https://cgit.FreeBSD.org/src/commit/?id=9dd78db9c30a220ac3e8e65d89548ff99c14dd90
commit 9dd78db9c30a220ac3e8e65d89548ff99c14dd90
Author: Osamu Sho <osamusho@gmail.com>
AuthorDate: 2025-09-04 02:34:34 +0000
Commit: Warner Losh <imp@FreeBSD.org>
CommitDate: 2025-09-14 03:09:26 +0000
libc: prevent incorrect %a/%La rounding at full precision
In __hdtoa() and __hldtoa(), rounding is incorrectly applied when
the requested precision exactly matches the number of significant
hexadecimal digits. In this case, the redux adjustment can trigger
an unintended exponent increment and shift the rounding position
left by one bit. This causes the least significant digit to be
rounded incorrectly.
The fix adds a new condition based on MAX_HEX_DIGITS (derived from
MANT_DIG) so that rounding is performed only when precision is
strictly less than the number of significant digits. This avoids
the unintended shift while preserving correct rounding for other
cases.
A new regression test
(printfloat_test:hexadecimal_rounding_offset_eq_exp) covers both
the binary64 (%.13a) and binary128 (%.28La on arm64) cases that
previously fail, ensuring the bug does not regress.
Note: MAX_HEX_DIGITS represents the maximum number of hexadecimal
digits needed to express the mantissa. It is computed by subtracting
the implicit integer bit from [L]DBL_MANT_DIG, dividing the remaining
mantissa bits by 4 (with +3 to round up any remainder), and finally
adding +1 for the leading integer digit. This makes its meaning
explicit and distinct from SIGFIGS, which serves a different purpose.
Fixes: 76303a9735ee ("Make several changes to the way printf handles hex floating point (%a):")
Signed-off-by: Osamu Sho <osamusho@gmail.com>
Reviewed by: imp,jlduran
Pull Request: https://github.com/freebsd/freebsd-src/pull/1837
---
lib/libc/gdtoa/_hdtoa.c | 3 ++-
lib/libc/gdtoa/_hldtoa.c | 3 ++-
lib/libc/tests/stdio/printfloat_test.c | 13 +++++++++++++
3 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/lib/libc/gdtoa/_hdtoa.c b/lib/libc/gdtoa/_hdtoa.c
index 8ae739acf0db..9c42630cd918 100644
--- a/lib/libc/gdtoa/_hdtoa.c
+++ b/lib/libc/gdtoa/_hdtoa.c
@@ -40,6 +40,7 @@
#define DBL_ADJ (DBL_MAX_EXP - 2)
#define SIGFIGS ((DBL_MANT_DIG + 3) / 4 + 1)
+#define MAX_HEX_DIGITS ((DBL_MANT_DIG + 3 - 1) / 4 + 1)
static const float one[] = { 1.0f, -1.0f };
@@ -111,7 +112,7 @@ __hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign,
s0 = rv_alloc(bufsize);
/* Round to the desired number of digits. */
- if (SIGFIGS > ndigits && ndigits > 0) {
+ if (MAX_HEX_DIGITS > ndigits && ndigits > 0) {
float redux = one[u.bits.sign];
int offset = 4 * ndigits + DBL_MAX_EXP - 4 - DBL_MANT_DIG;
u.bits.exp = offset;
diff --git a/lib/libc/gdtoa/_hldtoa.c b/lib/libc/gdtoa/_hldtoa.c
index 965d2349d103..5f10d12c5c09 100644
--- a/lib/libc/gdtoa/_hldtoa.c
+++ b/lib/libc/gdtoa/_hldtoa.c
@@ -65,6 +65,7 @@ typedef uint32_t manl_t;
#define LDBL_ADJ (LDBL_MAX_EXP - 2)
#define SIGFIGS ((LDBL_MANT_DIG + 3) / 4 + 1)
+#define MAX_HEX_DIGITS ((LDBL_MANT_DIG + 3 - 1) / 4 + 1)
static const float one[] = { 1.0f, -1.0f };
@@ -125,7 +126,7 @@ __hldtoa(long double e, const char *xdigs, int ndigits, int *decpt, int *sign,
s0 = rv_alloc(bufsize);
/* Round to the desired number of digits. */
- if (SIGFIGS > ndigits && ndigits > 0) {
+ if (MAX_HEX_DIGITS > ndigits && ndigits > 0) {
float redux = one[u.bits.sign];
int offset = 4 * ndigits + LDBL_MAX_EXP - 4 - LDBL_MANT_DIG;
#ifdef __i386__
diff --git a/lib/libc/tests/stdio/printfloat_test.c b/lib/libc/tests/stdio/printfloat_test.c
index 031859124163..795c7797541e 100644
--- a/lib/libc/tests/stdio/printfloat_test.c
+++ b/lib/libc/tests/stdio/printfloat_test.c
@@ -398,6 +398,18 @@ ATF_TC_BODY(subnormal_float, tc)
testfmt("-0X1P-149", "%A", negative);
}
+ATF_TC_WITHOUT_HEAD(hexadecimal_rounding_fullprec);
+ATF_TC_BODY(hexadecimal_rounding_fullprec, tc)
+{
+ /* Double: %.13a with binary64 mantissa=53 */
+ testfmt("0x1.1234567890bbbp+0", "%.13a", 0x1.1234567890bbbp + 0);
+
+#if defined(__aarch64__)
+ /* On arm64, long double is IEEE binary128 (mantissa=113) */
+ testfmt("0x1.3c0ca428c59fbbbbbbbbbbbbbbbbp+0", "%.28La", 0x1.3c0ca428c59fbbbbbbbbbbbbbbbbp + 0L);
+#endif
+}
+
ATF_TP_ADD_TCS(tp)
{
@@ -414,6 +426,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, hexadecimal_rounding);
ATF_TP_ADD_TC(tp, subnormal_double);
ATF_TP_ADD_TC(tp, subnormal_float);
+ ATF_TP_ADD_TC(tp, hexadecimal_rounding_fullprec);
return (atf_no_error());
}