Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
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.