Skip to content

Floating constants folded as the target computes them - #381

Merged
davidgiven merged 5 commits into
davidgiven:defaultfrom
maarten-boot:upstream/float-fold
Nov 2, 2026
Merged

davidgiven merged 5 commits into
davidgiven:defaultfrom
maarten-boot:upstream/float-fold

Conversation

@maarten-boot

Copy link
Copy Markdown
Contributor
  • Problem: cemcom.ansi keeps float constants in flt_arith's 64-bit mantissa,
    folds them at that precision and rounds to the target type only when it writes
    them out. A folded constant could then differ from the same computation at
    run time: in a probe of 300 random pairs, 11-39% of folded + - * / results
    (double and float) were 1 ulp off. 0.1 + 0.2 gave the double nearest 0.3,
    not 0.30000000000000004. A float constant also kept 64 bits, so
    (double)0.1f was the double 0.1 and 0.1f == 0.1 was true.
  • flt_arith (commit 2): add, sub, mul and div keep what does not fit as a
    sticky lowest bit (round to odd): the bits shifted out of the smaller operand
    in add/sub, the lower half of mul's 128-bit product (was rounded half-up),
    div's remainder (was dropped). New flt_rnd(e, bits, emin) rounds to bits
    bits, to nearest and ties to even, with subnormal numbers below 2^emin.
    With operands and result rounded by flt_rnd, a result is what IEEE
    arithmetic gives. Documented in flt_arith.3.
  • cemcom.ansi (commit 3): every float constant is rounded to its type with
    flt_rnd(): a literal, a folded result, an integer converted to a floating
    type, and a conversion between floating types. A 4-byte type is IEEE single
    and an 8-byte type IEEE double, subnormal numbers included. For targets
    without IEEE floating point, the new -Vm<n> gives the mantissa bits of an
    8-byte type; pdpv7 (PDP-11 floating point, 56 bits) uses -Vm56. Documented
    in cemcom.ansi.1.
  • flt_str2flt (commit 3): negative exponents were applied by multiplying
    with reciprocal powers of ten, which is not exact; with the sticky bit even
    1.0 came out as slightly more than 1, and an exact tie like
    9007199254740993.0 rounded the wrong way. It now divides by the exact powers
    10^0 to 10^27, and digits that do not fit in 64 bits set the sticky bit
    instead of being dropped. (A literal with more than 19 significant digits that
    is exactly a tie is still judged on its first 19 digits.)
  • Tests:
    • modules/src/flt_arith/ieeetest.c, rule modules/src/flt_arith+test, part
      of all (not on Windows): 1,580,135 random double and float operations
      (subnormal results, exact results and ties included) against the host's
      IEEE arithmetic. The old add, mul and div give 3, 24 and 38 wrong results.
    • tests/plat/floats/constfold,e,.c in the floats set (run on linuxppc):
      folded constants compared with the same expressions computed at run time,
      and with exact values: + - * / of double and float, float precision of
      float constants, 0.1 + 0.2 == 0.30000000000000004, ties rounded to even
      and subnormal results. With the compiler before this PR, 20 of its 26
      checks fail. Operands are plain globals, not volatile: the powerpc ncg
      table has no pattern for the nop cemcom.ansi emits after a volatile
      access (a separate fix).
  • linuxppc emulator (commit 1): constfold is the first test that
    multiplies, or does single-precision arithmetic, at run time on linuxppc,
    and the emulator (plat/linuxppc/emu/instructions.dat) could not run it.
    fmul was decoded as X-form with FRB, but it is A-form with FRA and FRC
    (as the assembler, mach/powerpc/as/mach3.c, encodes it): any multiply
    with FRC not f0 stopped with "unimplemented instruction", and one that
    matched computed FRA × FRB. fadds, fsubs, fmuls and fdivs
    (opcode 59) were missing; they compute in double and round to float,
    which is exact for these operations.
  • Not changed: the back ends' float_cst (h/con_float) still rounds
    half-up when it converts the constant text; the constants it now gets are
    already rounded, so that has no effect on IEEE targets. The Modula-2 front end
    folds with flt_arith too and is not changed here.

🤖 Generated with Claude Code

maarten-boot and others added 4 commits October 30, 2026 20:34
fmul was decoded as an X-form instruction with FRB, but it is A-form with
FRA and FRC, as the assembler (mach/powerpc/as/mach3.c) encodes it: a
multiply with FRC other than f0 stopped the emulator with "unimplemented
instruction", and one that matched computed FRA * FRB.

The single-precision fadds, fsubs, fmuls and fdivs (opcode 59) were
missing. They compute in double and round to float, which is exact for
these operations.

No test multiplied or did float arithmetic at run time on linuxppc before
tests/plat/floats/constfold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The front ends fold float constants with flt_arith, on 64-bit mantissas,
and the back end rounds the result to the target type only at the end.
To make that rounding exact, the operations now keep what does not fit
as a sticky lowest bit (rounding to odd): the bits shifted out of the
smaller operand of flt_add/flt_sub, the lower half of flt_mul's 128-bit
product (was rounded half-up at 64 bits), flt_div's remainder (was
dropped).

flt_rnd(e, bits, emin) rounds to bits bits, to nearest and ties to
even, with fewer bits below 2^emin as in a subnormal number. With
operands rounded by flt_rnd, the result rounded by flt_rnd to the same
precision is what IEEE arithmetic gives.

Test: modules/src/flt_arith/ieeetest.c, part of all: 1,580,135 random
double and float operations (subnormal results, exact results and ties
included) against the host's IEEE arithmetic. The old add, mul and div
give 3, 24 and 38 wrong results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Float constants were kept and folded with flt_arith's 64-bit mantissa
and rounded to the target type only when written out, so a folded
constant could differ from the same computation at run time: 11-39% of
folded + - * / results were 1 ulp off (0.1 + 0.2 gave the double nearest
0.3, not 0.30000000000000004). A float constant also kept 64 bits:
(double)0.1f was the double 0.1, and 0.1f == 0.1 was true.

Now every float constant is rounded to its type with flt_rnd(): a
literal, a folded result, an integer converted to a floating type, and
a conversion between floating types (which only changed the type). A
4-byte type is IEEE single and an 8-byte type IEEE double, subnormal
numbers included. For a target whose floating point is not IEEE, the
new -Vm<n> gives the mantissa bits of an 8-byte type; pdpv7 uses -Vm56.

flt_str2flt scaled the digits by multiplying with reciprocal powers of
ten, which is not exact; with flt_mul's sticky bit even "1.0" came out
as more than 1, and an exact tie like 9007199254740993.0 rounded the
wrong way. Negative exponents now divide by the exact powers 10^0 to
10^27, and digits that do not fit in 64 bits set the sticky bit
instead of being dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folded floating constants compared with the same expressions computed
at run time from volatile operands, and with exact values: + - * / of
double and float, float precision of float constants ((double)0.1f,
0.1f == 0.1), 0.1 + 0.2 == 0.30000000000000004, ties rounded to even
(9007199254740993.0, 16777217.0f, (float)16777217L) and subnormal
results. In the floats set. With the compiler before the two previous
commits, 20 of its 26 checks fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maarten-boot added a commit to maarten-boot/ack that referenced this pull request Oct 30, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@davidgiven
davidgiven merged commit c8caa95 into davidgiven:default Nov 2, 2026
0 of 3 checks passed
@davidgiven

Copy link
Copy Markdown
Owner

Merged; thanks!

@maarten-boot
maarten-boot deleted the upstream/float-fold branch November 2, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants