← board

Extended is silently an alias for Double

This is the UMBRELLA ticket for the whole Extended cluster (designated 2026-08-30). Three overlapping tickets existed in three folders; this one had the only real scope analysis, so it absorbed the others. Index at the bottom.

User, 2026-08-10: "we also need to support extended in the future." Filed from the float-formatting discussion; deliberately not urgent.

Measured (x86-64, {$mode objfpc}, HEAD f20a7363d)

FPC pxx
SizeOf(Extended) 10 8
e := 1.0/3.0; WriteLn(e) 3.33333333333333333342E-0001 3.3333333333333331E-001

pxx accepts Extended and maps it to Double. It compiles, it runs, and it is silently less precise — which is the part that matters: FPC code that uses Extended deliberately (accumulators, iterative refinement, anything relying on 80-bit intermediates to avoid drift) gets a quietly worse answer with no diagnostic. That is a compat trap, not merely a missing type.

Scope, honestly

This is not a formatting change — it is a TYPE with 80-bit x87 semantics:

Interim, cheap option worth considering first

If full 80-bit is not wanted soon, a diagnostic beats silence: warn (or error under --strict-fpc) when Extended is declared on a target where it aliases Double, so a port that depends on the precision finds out at compile time instead of drifting. That is a small change and removes the trap without the x87 work.

The default-output-format question for Extended (FPC prints 20 significant digits and a 4-digit exponent) is deliberately postponed — rainy-day/decide-default-float-output-format-and-constant-precision. Note that question becomes moot for Extended if this lands, since the digits would then be real rather than Double's rendered wider.

Gate

SizeOf(Extended) = 10 on x86-64 and = 8 elsewhere; the 1/3 row matching FPC to 20 significant digits; a record containing an Extended field laying out with FPC-compatible offsets; make test + self-host byte-identical + cross.

<!-- float category --> Indexed on [[meta-float-accuracy-policy]] — the standing float-accuracy index. Collect, do not fix piecemeal; see the working rule there.

Triage 2026-08-19 (Track D re-triage pass, pin v363)

Genuine feature, still wanted, numbers unchanged. Re-measured rather than copied — SizeOf(Extended) is 8 under pxx and 10 under FPC 3.x, and 1.0/3.0 prints 3.3333333333333331E-001 against FPC's 3.33333333333333333342E-0001. Identical to the table filed on 2026-08-10.

Deliberately not re-typed as a bug despite being silent: the repo's float policy is fast-by-default, and this is a type that is not implemented rather than an arithmetic result that drifted. The scope note is what matters here — x86-64-only, x87 rather than SSE, 10-byte storage with 16-byte padding — and it is why the low prio is right.


RULING 2026-08-30 (owner) — Extended WILL be implemented, eventually

"eventually we will implement 80-bit extended type properly. for now, we move all related tickets to the float subfolder. so we can work on those tickets in a consolidated session."

This settles the direction that feature-extended-alias-or-reject left open in 2026-06-22, when option (c) real 80-bit was recorded as "explicitly NOT wanted". It is now wanted — later, and as one piece of work, not piecemeal.

Two things follow, and the second is the operative one:

  1. The alias is interim, not the permanent answer. done/feature-extended-alias-or-reject should be read as a holding position, not a closed design question.
  2. Nothing in this cluster is worked on its own. That is the entire reason the folder move happened: the cost here is not any single sub-part, it is that the parts only make sense together — a 10-byte type nothing can print, or an x87 path no math routine reaches, is worse than the honest alias we ship today. Parked in float/, invisible to ready/next, picked up on explicit request.

What "properly" has to cover — the consolidated session's scope

The user's own framing, 2026-08-30: "this has more aspects than data type alone ... it also involves math library and formatting functions, and using the floating point stack registers." Correct, and that is four workstreams:

# aspect where note
1 the type: 10-byte storage, 16-byte record/array padding, SizeOf defs.inc, symtab.inc, pasparser_lval.inc get padding wrong and every Extended field offset in a ported record is wrong
2 x87 codegenfld/fstp tbyte, a second register file, its own control word and rounding story ir_codegen.inc (x86-64), i386 the SSE2 path cannot express 80-bit; this is not a widening of existing code
3 RTL formatting: Str, Val, FloatToStr, WriteLn's formatter lib/rtl FPC prints 20 significant digits and a 4-digit exponent for Extended
4 math library: lib/rtl/math.pas today provides Single + Double overloads only, deliberately, because Extended is the alias lib/rtl/math.pas (Track B) inherited from the folded-in feature-extended-type-support

Plus the cross-target question that has no good answer and must be decided rather than discovered: aarch64, arm32, riscv32, xtensa and wasm32 have no 80-bit format. FPC itself falls back to Double for Extended on those targets, so SizeOf(Extended) becomes target-dependent (10 on x86-64/i386, 8 elsewhere) exactly as it is under FPC. Code that assumes 10 breaks on the cross targets — under FPC too, which is the argument that it is acceptable.

The cluster — index

ticket state relation
this file float/ the umbrella; scope + ruling live here
[[feature-extended-type-support]] float/ superseded by this ticket; kept as a gravestone, its RTL constraint folded in above
[[decide-is-real-a-double-or-fpcs-80-bit-extended]] float/ Track U. Partly answered by the ruling above — but the residual question stands: does the bare name Real follow Extended on x86-64, or stay Double for cross-target coherence? Answer before starting workstream 1.
[[bug-p-sizeof-extended-disagrees-with-the-storage-extended-gets]] backlog/ NOT blocked by this ticket, and un-parked 2026-08-30 — under a permanent Extended = Double alias a wrong SizeOf is the compiler lying about its own type, which is correctness, not float work. Ranked normally.
[[decide-default-float-output-format-and-constant-precision]] float/ workstream 3's parent question; goes moot for Extended if this lands, since the digits become real rather than Double's rendered wider
[[bug-n-nilpy-carries-its-own-copies-of-the-float-type-table]] backlog/ left ranked deliberately (it is a duplication bug worth fixing regardless), but pyparser.inc:966 collapses single and extended into tyDouble in a private table — workstream 1 must land there too, or NilPy silently keeps the alias
done/feature-extended-alias-or-reject done the 2026-06-22 holding position; now interim per the ruling
done/bug-c-crtl-long-double-math done C's long double — the other frontend that will want workstream 2

The sizeof bug is deliberately NOT gated on this umbrella

bug-p-sizeof-extended-disagrees-with-the-storage-extended-gets is a self-inconsistency inside the current alias, not a step toward the real type: pasparser_lval.inc:6304 (declaration) says tyDouble/8 while :6417 (SizeOf) says tyExtended/10. Measured 2026-08-30 on this tree: :6417 is the only site in the entire compiler that PRODUCES tyExtended — every other reference (ir_codegen, the backends, cparser's promotion rules at cparser.inc:133/:172) is a consumer that only fires on an operand that is already Extended.

So fixing it makes tyExtended genuinely dead, which is the cleanest starting position for this umbrella, not a conflict with it: when real Extended lands, both tables move together, in one place, instead of the split having to be re-merged first. It makes the big job smaller. Work it whenever; do not wait.

Interim, still worth doing before any of the above

The diagnostic proposed further up — warn (error under --strict-fpc) when Extended is declared on a target where it aliases Double. The trap today is not the missing precision, it is the silence: a ported FPC numeric routine drifts with no compile-time signal. Small change, removes the trap, and does not pre-commit workstream 2.


The FPC target spec, MEASURED 2026-08-30 — this is what "properly" has to hit

Owner, 2026-08-30: "not sure what extended would be on ARM - i suspect 64 bit, so extended being 80 bit is intel only? and, i'm not sure what to expect for alignment (arrays/records). but still, we follow what FPC does."

Both suspicions confirmed, and the alignment answer is stranger than either guess. Measured against FPC 3.2.2 (x86-64, {$mode objfpc}, -O2, default {$PACKRECORDS}) plus FPC's own rtl/inc/systemh.inc. This table is the specification for workstream 1 — do not re-derive it, and do not assume C's long double rules, which differ on every line that matters.

1. Extended is Intel-only — confirmed from FPC's source

SUPPORT_EXTENDED is defined for exactly three CPUs in rtl/inc/systemh.inc:

CPU default float SUPPORT_EXTENDED
CPUI386 DEFAULT_EXTENDED yes
CPUI8086 DEFAULT_EXTENDED yes
CPUX86_64 DEFAULT_EXTENDED only if FPC_HAS_TYPE_EXTENDED, else DEFAULT_DOUBLE conditional
CPUARM, CPUAARCH64, CPUM68K, CPUPOWERPC, CPUSPARC, CPUSPARC64 DEFAULT_DOUBLE no
CPUAVR DEFAULT_SINGLE no

So Extended is 8 bytes on ARM and AArch64 — the suspicion was right — and on every other non-x86 target. It is 10 bytes on i386 and on x86-64 with the legacy FPU. Note the conditional: FPC's own comment is "win64 doesn't support the legacy fpu", so Extended is Double on x86-64 Windows too. That is a Track M consideration, not just a cross-target one: the PE/COFF + MS x64 ABI work must NOT get 80-bit Extended even though the CPU is x86-64.

2. Sizes and layout — measured, x86-64 Linux

SizeOf(Single) 4   SizeOf(Double) 8   SizeOf(Extended) 10   SizeOf(Real) 8
SizeOf(Comp)   8   SizeOf(Currency) 8
construct FPC note
array[1..3] of Extended size 30, stride 10 no padding in arrays
record b: Extended end size 16 padded up
record a: Byte; b: Extended end size 32, off(b)=16 field is 16-aligned
record a: Byte; b: Extended; c: Byte end size 32, off(b)=16, off(c)=**26** the next field packs immediately after the 10 bytes — no trailing pad before c
packed record a: Byte; b: Extended; c: Byte end 12 1+10+1
record a: Byte; b: Double; c: Byte end size 24, off(b)=8, off(c)=16 Double for contrast

The rule, stated once: Extended is a 10-byte type with 16-byte alignment. Alignment governs where it starts (record fields round up to 16; a record containing one rounds its own size to 16) and does not govern what follows it (the next field sits at +10, and array elements stride by 10).

That combination is unusual and is where an implementation will go wrong. The earlier note in this ticket — "FPC pads to 16 in records/arrays on x86-64" — is half wrong and now corrected: records yes, arrays no.

3. It is NOT C's long double — an interop trap for Track C

Same box, gcc 13:

sizeof(long double)     = 16      _Alignof(long double) = 16
struct{char,ld,char}    = 48      off(b)=16
long double[3] stride   = 16
FPC Extended C long double
SizeOf 10 16
array stride 10 16
record/struct field alignment 16 16

A Pascal array of Extended and a C long double[] do not have the same layout, so passing one to the other desynchronises after the first element. lib/crtl and any long double binding must convert, not alias. Same trap the sizeof bug describes, but across a language boundary rather than inside one compiler — and it lands on [[bug-c-crtl-long-double-math]]'s territory.

4. Real is settled, and it was never part of this

Real = type Double unconditionally in FPC (systemh.inc:117, no CPU guard). Real never becomes Extended anywhere, and a Real variable under FPC prints byte-for-byte what pxx prints today. The writeln(3.14159) divergence that started this cluster is constant precision (DEFAULT_EXTENDED), not Real's width — measured and written up in [[decide-is-real-a-double-or-fpcs-80-bit-extended]], which surfaces a genuinely new question in its place (pxx makes Real Single on riscv32/xtensa; FPC does not).

Reproduce

Both probes are small and self-contained; the tables above were produced by fpc -O2 on the two programs quoted in this section plus a gcc -O2 one-liner for the C column. Re-measure rather than trusting this page if FPC's version moves — SUPPORT_EXTENDED is version-conditional by construction.


DIRECTION 2026-08-30 (owner) — soft_extended, and why it changes the shape

"a 'correct' way to solve it would be to emulate extended type (soft_extended), for the cases where the programmer needs/expects this precision, at a (severe) performance cost. for example, planetary or satellite orbit simulations etc.. on the other hand, i seen programmers use 'extended' because 'it had more precision', even if that was insignificant for the task and just lead to a performance penalty."

Not a footnote — it reverses which workstream is the foundation, and it is the first proposal in this cluster that makes the hard parts go away instead of distributing them.

What a software 80-bit type dissolves

Everything measured in the section above that made this expensive was a consequence of binding Extended to hardware:

problem with x87 as the foundation with soft_extended
aarch64/arm32/riscv32/xtensa/wasm32 have no 80-bit format Extended means something different per target; SizeOf becomes target-dependent gone — one format, every target, 10 bytes everywhere
x86-64 Windows has no legacy FPU (FPC_HAS_TYPE_EXTENDED off for win64) the same CPU needs two answers; a Track M landmine gone
the SSE2 path cannot express 80-bit, so workstream 2 is a second register file, its own control word and rounding story prerequisite — nothing else can start optional — a fast path for x86-64, added later or never
Str/Val/FloatToStr/WriteLn need 20-digit output from a hardware format coupled to x87 register moves plain code over a struct we own

The cross-target question that this ticket calls "the question that has no good answer and must be decided rather than discovered" stops being a question. Software emulation is uniform by construction, which is precisely the property the original filing said mattered more than matching FPC: pxx's own targets disagreeing with each other is worse than pxx disagreeing with FPC.

So the workstream order inverts. Old: x87 first, everything else on top of it. New: the soft type first (format, arithmetic, Str/Val, math overloads) — portable, testable against FPC on x86-64 as the oracle, no backend work at all — and x87 becomes an optimisation of an already-correct implementation on the one family that has the hardware. That also means the whole thing can be built and gated without touching ir_codegen.inc, which moves the bulk of it out of Track A's shared ground.

Cost is the stated one: severe, and paid only by code that asked for it by name.

The other half — why this stays deferred, sharpened

"i seen programmers use 'extended' because 'it had more precision', even if that was insignificant for the task and just lead to a performance penalty ... we support floats, and we are chasing ancient windmills."

This is the strongest version of the F charter's argument, and it is specifically an argument about who the feature is for:

Hardware x87 serves the second group at great implementation cost and serves the first group no better than software does. That is the whole case for deferral in one line: the expensive half of this feature exists to make a cargo-cult idiom fast.

Which is also why the cluster stays parked rather than rejected. The real use case is real; it is just rare, and it is best served by the cheap-to-build, portable half. If this is ever picked up, start with soft_extended and stop there until something measured asks for more.


OPTION 2026-08-30 — if it is emulated anyway, emulate binary128, not x87's 80-bit

Follows directly from the soft_extended direction above. Once the decision is "software", the 80-bit format stops having anything to recommend it, and the choice of which wide format to emulate is suddenly free.

x87 80-bit (Extended) IEEE 754 binary128
mantissa 64 bits 113 bits
storage 10 bytes, 16-byte aligned, array stride 10 — the whole mess measured above 16 bytes, 16-aligned, stride 16. No special case anywhere
status a 1980s x87 artifact; only FPC-on-Intel and C's long double speak it the actual IEEE standard type
who else has it (2026) almost nobody by name C _Float128/__float128 (C23 + libquadmath), C++23 std::float128_t, Fortran real128, Zig f128 as a builtin primitive, Julia Float128, Rust f128 (nightly)
hardware x86 x87 only, and not on win64 POWER9/10, IBM z. Not x86, not ARM — software everywhere we target

Since it is software on every pxx target either way, binary128 costs the same to build and delivers strictly more precision than FPC's Extended, with none of the layout weirdness that makes workstream 1 fiddly. Extended would become the name that maps to it — a superset of what FPC promises, which is the comfortable direction to diverge (CLAUDE.md: we accept a form FPC rejects → not a defect).

It also lands where the rest of the world already is. Track Z is the sharpest illustration: Zig's primitive float set is f16/f32/f64/f80/f128, so a Zig frontend will eventually want f128 regardless of what Pascal's Extended does, and building it once in the IR serves both — exactly the ir-as-substrate.md argument.

The Python cautionary tale, measured 2026-08-30 on this box

Worth recording because it is the exact mistake this option avoids:

numpy 2.3.5, x86-64
np.float128 itemsize = 16 bytes,  finfo declares 128 bits
   ...but nmant = 63              -> it is x87 80-bit extended
1/3 = 0.33333333333333333334      -> 20 digits, identical to FPC's Extended
np.float128 is np.longdouble      -> True

true __float128 on the same box:  mantissa 113 bits
1/3 = 0.333333333333333333333333333333333317

numpy.float128 is the platform long double padded to 16 bytes; the "128" is storage, not precision. And on aarch64 — where ARM's long double genuinely is binary128 — the same name means 113 mantissa bits instead of 63. One name, two precisions, depending on the machine. That is precisely the per-target divergence this cluster keeps trying to avoid, shipped by a major library, and it is what naming a type after its byte count buys you.

CPython itself has no wide float at all: float is C double, and real precision work goes to decimal, mpmath or gmpy2 (MPFR).