← board

A decimal tie rounds down here, up in FPC

Found 2026-08-25 by Track B while adding FPC's four-argument FloatToStrF (feature-b-rtl-gap-inventory-22-sysutils-strutils-symbols). Not introduced by that work — the same divergence is already reachable through Format('%.2f'), which has been in lib/rtl/sysutils.pas all along. Nineteen of the twenty FloatToStrF cases in that ticket's probe are byte-identical to FPC; this is the twentieth.

Measured (fpc 3.2.2 vs pxx pinned stable, LC_ALL=C)

expression FPC pxx
Format('%.2f', [1.005]) 1.01 1.00
Format('%.2f', [-1.005]) -1.01 -1.00
Format('%.2f', [2.675]) 2.68 2.67
Format('%.2f', [0.125]) 0.13 0.13 (agree)

Same three rows through FloatToStrF(v, ffFixed, 15, 2), and they will show up in ffNumber and ffCurrency too, since all four route through FmtFixed.

Which one is right

Arguably ours. 1.005 is not representable: the nearest Double is 1.00499999999999989341858963598497211933135986328125, which is BELOW the tie, so 1.00 is the correctly-rounded two-place rendering of the value that actually exists. FPC reaches 1.01 by rounding a decimal intermediate rather than the binary value.

0.125 agrees because it IS exactly representable, so both implementations see a genuine tie and both round half-away-from-zero on it.

So this is not "we are wrong", it is "we round the value and FPC rounds the literal". Worth knowing before anyone changes it toward FPC and makes the other cases worse.

Why it is Track F and not an ordinary bug

The subject is float RENDERING, and the disagreement is confined to the last digit — exactly the "insignificant digits" case the owner named. Nothing is missing, nothing crashes, no signature is wrong. Per CLAUDE.md's F rule this parks in float/ and is not ranked.

The escape hatch does not apply: nobody is getting a silently wrong ANSWER, only a differently-rounded last digit of a printed one.

If it is ever picked up

The fix is in FmtFixed (lib/rtl/sysutils.pas), and it is one decision: whether the rounding happens on the exact decimal expansion of the Double (as ExDecDigits/ExDecRound already do for FmtExponent) or on a scaled intermediate. FmtExponent was rewritten onto the exact path for a related reason and its note records what that bought; FmtFixed was not.

Gate

Track B: make lib-test, plus the four rows above compared against fpc -O- -Mobjfpc -Sh output for the same program.