Story: Hotfix: the macOS parser refuses the smallest subnormal

Table of Contents

This page documents a story in Sprint 26. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

1. Goal

The macOS test phase fails one assertion:

projects/ores.platform/tests/numeric_floating_point_tests.cpp:93: FAILED:
  parsed.has_value() for: false

The case formats each of a set of doubles with @c std::to_chars and reads each back with ores::platform::numeric::parse_double. On Apple platforms libc++ formats the smallest denormal as 5e-324, and the conversion the shim used – boost::lexical_cast through a stream – reports the range error the C library raises for a subnormal result as a stream failure, so the parse answered nullopt for a value a double holds. Linux and Windows report the same range error without failing the extraction, which is why only macOS saw it.

The header promised a parser that refuses "a value outside the range of a double". A subnormal is inside that range, and the shim refused it by accident on one platform; 1e-400, which really is outside it, was accepted by accident on the others.

2. Status

Field Value
State DONE
Parent sprint Sprint 26
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-30

3. Acceptance

  • parse_double("5e-324") returns the denormal, not nullopt, so the round trip passes on macOS.
  • A value that rounds to zero or to infinity is refused, which is what the header always claimed.
  • The parse does not follow the program's locale: with a comma locale in force, an English number still parses and a comma one is still refused.
  • The conversion does not go through a stream, so libc++'s treatment of the range error cannot decide the answer.
  • The platform suite and the full suite pass.

4. Tasks

Task State Start End Description
Implement Hotfix: the macOS parser refuses the smallest subnormal DONE 2026-09-30 2026-09-30 Replace the stream conversion with a hand-written grammar check and a C-locale conversion, add the subnormal and underflow cases to the suite, and prove the locale independence on Linux.

5. Decisions

  • The grammar is checked in the shim rather than left to the conversion, so the accepted syntax depends on neither the program's locale nor the C library's extensions: hexadecimal input, inf and nan are not numbers.
  • The conversion runs in a C locale of its own – strtod_l, _strtod_l on Windows, with the macOS header behind __has_include – rather than in a stream. It is the stream, not the C library, that decides a subnormal is a failure on libc++.
  • A result that rounds to zero is refused even though it is finite: the type cannot tell it from a real zero, and the header calls that outside the range. The old parser accepted it, and no caller depended on that.
  • The two hotfixes of this evening are separate branches. This one shares no file with the exported-type one.

6. Out of scope

  • libc++'s missing floating-point @c from_chars, which the shim exists to stand in for. Nothing here changes when Apple implements it; the shim can then be deleted.

Emacs 29.3 (Org mode 9.6.15)