|
|
Message-ID: <20260805140804.835542-1-matthias.goergens@gmail.com>
Date: Wed, 5 Aug 2026 22:08:04 +0800
From: Matthias Goergens <matthias.goergens@...il.com>
To: Szabolcs Nagy <nsz@...t70.net>
Cc: Matthias Goergens <matthias.goergens@...il.com>,
musl@...ts.openwall.com
Subject: Re: [PATCH] stdio: reserve leading limb for floating-point rounding
You're right, and the patch should be withdrawn. My analysis missed
the mechanism you described.
The missing piece: in the e2<0 path the value is always in (0,1):
frexpl gives y in [1,2) with v = y*2^e2, so e2<0 implies v<1, and
after y *= 0x1p28 we have y < 2^29 with e2 <= -29. The right-shift
loop therefore divides the limb array by at least 2^29, which always
leaves the most significant limb zero, and the loop's
if (!*a) a++;
then guarantees a >= big+1 before rounding. The rounding carry's
if (d<a) *--a=0;
consequently writes big[0] — the dead units limb — never big[-1].
Exactly the "a = d = big+1, big[0] = 0 before the while" trace you
walked.
Because our claim said the opposite, I verified this empirically rather
than just by re-reading the code. I built unpatched master (b306b16a)
with a canary in fmt_fp that aborts if `*--a` is ever reached with
a<=big (in both the rounding loop and the positive-exponent scaling
loop), plus offset tracing. Results:
- Stated trigger printf("%.0Lf", nextafterl(1.0L, 0.0L)): the
carry-past-a path is reached, with a=d=big+1 and big[0]=0 — your
reading confirmed concretely. The write lands on big[0].
- A ~4.2M-call sweep (ulp neighbourhoods below/above 1.0L and powers of
two/ten, 1-2^-k, extrema, denormals, precisions 0..1000, %f/%e/%g,
double and long double) on x86_64: 9,090 carry-past-a events, every
one with a-big=1, canary never fired.
- Same sweep under QEMU on aarch64 (binary128, LDBL_MANT_DIG==113):
20,684 carry-past-a events, minimum slack a-big=1, canary never
fired; and on arm (LDBL_MANT_DIG==53): likewise.
- Our 62,304-case corpus and the extrema/rounding-mode matrices also
run clean under the canary, with output hashes unchanged.
Where the original claim went wrong: it keyed on the initial assignment
`if (e2<0) a=r=z=big;` and assumed a==big persists into the rounding
loop, overlooking that the right-shift loop vacates big[0] first. The
trigger does exercise the carry-past-a branch (that part was real), but
the write is in bounds.
Net: the claimed big[-1] write does not occur; the patch reserves a
slot that the vacated-units-limb invariant already provides, so it is a
semantic no-op. I'm withdrawing it. Sorry for the noise, and thanks
for the careful walk-through.
Matthias
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.