← board

Format('%u', ...) is a synonym for %d

Repro

uses sysutils;
var q: QWord; i: Integer;
begin
  q := 18446744073709551615; i := -1;
  writeln(Format('%u', [q]));   { FPC: 18446744073709551615   pxx: -1 }
  writeln(Format('%u', [i]));   { FPC: 4294967295             pxx: -1 }
end.

Cause

lib/rtl/sysutils.pas, in the specifier case:

'd', 'u':
  piece := FmtIntPrec(IntToStr(FmtArgInt(args[argIdx])), hasPrec, prec);

One arm for two specifiers, and the shared body is signed. %x right below it already does the whole job correctly — it calls FmtArgIs32 to recover the argument's original width from the variant tag, because FmtArgInt widens everything to Int64 on the way in. %u needs exactly that and does not have it.

Fix sketch

Split the arm. For u, print unsigned at the argument's own width, mirroring what %x already does:

'u':
  begin
    iv := FmtArgInt(args[argIdx]);
    if FmtArgIs32(args[argIdx]) then piece := IntToStr(Int64(LongWord(iv)))
    else piece := <unsigned 64-bit decimal of iv>;
    piece := FmtIntPrec(piece, hasPrec, prec);
  end;

The 64-bit half needs an unsigned decimal renderer — IntToStr takes Int64. StrQWord in compiler/builtin/builtin.pas is the compiler-side one; sysutils needs its own (or a QWord overload of IntToStr), which is the only real work in this ticket.

Two more Format gaps found by the same probe

Separate, lower value, recorded here so they are not rediscovered:

case FPC pxx
Format('%p', [Pointer(1)]) 0000000000000001 %p (specifier unsupported, echoed literally)
Format('%d', [c]), c: LongWord = 4294967295 -1 4294967295
Format('%c', ['A']) (empty) A

The %d-of-a-Cardinal row is a compat divergence, not a bug in the %u sense: FPC passes a 32-bit unsigned as vtInteger and prints its bit pattern signed, pxx passes the value and prints it as-is. pxx's answer is the more useful one and nothing silently corrupts, so it is a deliberate-divergence candidate rather than a fix — but it should be a DECIDED divergence, since a program ported from FPC will see it. Same for %c, where FPC prints nothing at all for a one-character string literal.

Gate

Track B's: make lib-test / make demos with $(PXX_STABLE), plus a probe comparing every specifier against fpc -O- -Mobjfpc 3.2.2 — the ten-line one in this ticket's repro is a good start and should be checked in with the fix.

Log

Resolution (2026-08-27, frankB)

Fixed as one of a three-ticket cluster — see [[bug-b-inttostr-of-a-qword-prints-it-signed]] for the full write-up. In short: lib/rtl/sysutils.pas had no unsigned decimal renderer at all, so one (UIntToStr(QWord)) was added and IntToStr(QWord) plus Format's now-split %u arm both route through it, instead of each carrying its own signedness decision. Regression: test/lib_qword_render.pas + .expected in make lib-test, 29 rows byte-identical to fpc 3.2.2.