x86-64: writeln(d:w:n) saturates at Int64 for large values
- Type: bug — Track A (x86-64 native float emitter)
- Status: done
- Opened: 2026-08-05
- Found by: Track A, comparing float output across targets while fixing
bug-a-writeln-nonfinite-float-aarch64-emitters-unchecked. Pre-existing inpinned.
Repro
var d: Double;
begin
d := 1e20; writeln(d:0:2);
d := 123456789012345678.0; writeln(d:0:2);
end.
1e20:0:2 |
1.23456789012345678e17:0:2 |
|
|---|---|---|
| FPC | 100000000000000000000.00 |
123456789012345680.00 |
| pxx aarch64 / i386 / arm32 | 100000000000000000000.00 |
123456789012345680.00 |
| pxx x86-64 | 9223372036854775809.00 |
123456789012345680.00 |
9223372036854775809 is 2^63 + 1 — the value saturated when scaled into an
Int64. Silent: it is a plausible-looking number, and only the first row is
affected because the second still fits.
Why x86-64 is the odd one out
The cross targets route writeln(d:w:n) through the runtime helper
PXXWriteFloatFixed, which handles the large-magnitude case. x86-64 keeps a
hand-written native emitter (EmitWriteFloatFixed in symtab.inc) that scales
through Int64. This is the same "N copies of one formatter, and the copies
disagree" shape as
bug-a-writeln-float-exponent-form-not-correctly-rounded, which collapsed the
Sci formatters from four copies to one.
Second, independent repro (2026-08-05)
bug-a-x86-64-qword-to-double-assign-halves-above-2-63 was filed believing the
QWord→Double conversion halved. It does not — measured, d := q for
QWord max produces exactly FPC's double and compares equal to the literal
18446744073709551616.0. What it was actually seeing was THIS bug:
q := 18446744073709551615; d := q;
writeln(d); { pxx 1.8446744073709552E+019 = FPC }
writeln(d:0:1); { pxx 9223372036854775809.0 FPC 18446744073709552000.0 }
Same double, two spellings, only the fixed one wrong. That ticket is closed as a duplicate of this one. Worth knowing because it means this bug has already cost one wrong diagnosis: it presents as an arithmetic fault somewhere else.
Fix
Shim EmitWriteFloatFixed onto PXXWriteFloatFixed, as the Sci emitter now
is. Blocked on one thing: the runtime helper does not take a field WIDTH and
x86-64's emitter does, so shimming it today would silently drop padding — the
exact defect bug-a-aarch64-float-field-width-ignored describes on the other
targets. Add the width to the helper first, then this becomes a two-line shim
and the last hand-written float formatter goes away.
Resolution (2026-08-05) — fixed for the reachable cases
EmitWriteFloatFixed now routes to the runtime PXXWriteFloatFixed whenever
there is no field width to honour and the RTL is linked. That is the case in
every repro on this ticket — writeln(1e20:0:2) and d := QWordMax; writeln(d:0:1) both pass width 0 — so both are fixed:
writeln(1e20:0:2) 9223372036854775809.00 -> 100000000000000000000.00 (= FPC)
writeln(1.23456789012345678e17:0:2) -> 123456789012345680.00 (= FPC)
The native emitter survives ONLY for width > 0, because the runtime helper
still takes no field width and shimming those today would trade a wrong number
for lost padding — the exact defect
bug-a-aarch64-float-field-width-ignored describes on the other four targets.
Padding is verified unchanged: writeln(3.14159:10:4) still prints
3.1416 and writeln(-2.5:8:3) still prints -2.500, both matching FPC.
To finish it: give PXXWriteFloatFixed a width parameter (that is the
other ticket), then delete EmitWriteFloatFixedNative entirely and the shim
becomes unconditional — the same collapse EmitWriteFloatSci already had. That
would leave x86-64 with no hand-written float formatter at all.
Verified: testmgr --tier native 1158/1158 pass (includes the self-host
fixedpoint). i386/arm32/aarch64 byte-identical to before — the change is
confined to the x86-64 emitter.
Log
- 2026-08-05 — resolved, commit 0e3fd281e.