The branch main has been updated by imp:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=9dd78db9c30a220ac3e8e65d89548ff99c14dd90

commit 9dd78db9c30a220ac3e8e65d89548ff99c14dd90
Author:     Osamu Sho <osamu...@gmail.com>
AuthorDate: 2025-09-04 02:34:34 +0000
Commit:     Warner Losh <i...@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 <osamu...@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());
 }

Reply via email to