270: Hexadecimal Floating-Point Constants

by Peter McGoron

Status

This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-270@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

Floating-point numbers are usually stored in radix 2, but are written by users in radix 10. This SRFI introduces Scheme syntax for hexadecimal floating point constants based on C99's syntax, that use radix 16 for writing the integer and fractional part, and a radix 10 exponent part that raises the whole value to a power of 2.

Issues

Rationale

Computers generally use binary floating point to store numbers, while humans like decimals. To express binary floating point exactly, one can use hexadecimal floating point constants, which are written in base 16 instead of base 10 and whose exponential parts raise the given values to a power of 2 instead of a power of 10. They represent binary floating point numbers of any significand and exponent size precisely.

This format is based off of C99's hexadecimal floating point constant syntax. MIT Scheme implements a subset of this syntax. C, Julia, Java, and Zig allow one to write floating point numbers using IEEE 754-2008's syntax (which was derived from C99).

Specification

Non-normative text is in small text.

Syntax

The following is written in Scheme's BNF, extended with the following: ⟨thing⟩? means zero or one of ⟨thing⟩.

The formal syntax of Scheme is modified to have the following new productions:

⟨ureal R
::= ⟨hex float body R⟩ ⟨hex float suffix⟩
⟨hex float body 16⟩
::= ⟨uinteger 16⟩
| . ⟨digit 16⟩+
| ⟨digit 16⟩+ . ⟨digit 16⟩*
⟨hex float suffix⟩
::= p ⟨sign⟩? ⟨digit 10⟩+

Without an exactness prefix, a hex float is read as an inexact number. Like all numbers, hex floats can be read as exact numbers by prefixing them with #e.

The exponential separator is p because it is ambiguous whether e would be a decimal separator or a hex digit.

A hexadecimal floating point number is interpreted such that

xnxn−1x0.x−1x−2x−mpd

where each x is a hexadecimal digit and d is a base 10 number, is equal to

(Xn×16n + Xn−1×16n−1 + … + X0 + X−1×16−1 + X−2×16−2 + … + X−m×16m)×2d

where Xi is the numerical value of the digit xi.

R6RS Version

On R6RS systems, the above grammar is modified to be:

⟨ureal R⟩
::= ⟨hex float body R⟩ ⟨hex float suffix⟩ ⟨mantissa width⟩
⟨hex float suffix⟩
::= ⟨hex exponent marker⟩ ⟨exponent marker⟩? ⟨sign⟩? ⟨digit 10⟩+
⟨hex exponent marker⟩
::= p
| P

The R6RS considerations for numbers with ⟨exponent marker⟩ and ⟨mantissa width⟩ apply here.

This grammar is a superset of the previous grammar.

Possible Extensions

This section is non-normative.

Procedures

The procedure string->number MUST understand hexadecimal floats.

(write-hexadecimal-float z [port])

Write an inexact real as a floating point number. If the number is represented by an IEEE 754 binary floating point number, then the number MUST be represented in a certain form. If the number is a real, then:

If the number is complex, then each component must be printed with the above rules.

If an implementation implements IEEE 754 binary floating point numbers, then such a number written with this procedure MUST be read back as the same number according to eqv?.

If z cannot be represented as a finite hexadecimal float, then it is represented to an implementation defined precision. The implementation SHOULD write the closest flonum to z.

No prefix (such as #x or #e) is written.

An implementation should output the minimum length string that satisfies these requirements.

Examples

This section is non-normative.
Expression Number
#x9p9 9×29 = 4608
#x1.2p3 (1 + 2×16−1)×23 = 9
#xFE.FFp1 (15×161 + 14 + 15×16−1 + 15×16−2)×21 = 65279/128 ≈ 509.992…
#x-0.Ap-2 (10×16−1)×2−2 = −5/32 = −.15625
#x1.9p1+10p1i (1 + 9×16−1)×21 + (1×16 + 0)×21i = 25/8 + 32i

Implementation

A portable implementation is impossible, as it depends on both modifying the reader and knowing the specific representation of inexact reals.

An implementation written for Chez 10.4.0 is in the SRFI repository. The implementation includes a patch to the reader and test suite that automatically generates hex floats to test against.

The implementation of write-hexadecimal-float depends on a procedure decode-float, which is implemented using R6RS bytevector operations (assuming inexact reals are IEEE binary64). Such a procedure can be implemented using SRFI 144 flonums, using a procedure in the repository contributed by Sergei Egorov.

Acknowledgements

Taylor Campbell suggested it to me on #scheme.

© 2026 Peter McGoron.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler