quad-fp: quadruple-precision floating point
| (require quad-fp) | package: quad-fp |
A toy Racket FFI binding to GCC’s libquadmath, exposing IEEE 754 quadruple-precision (128-bit, __float128) floating point — roughly 34 significant decimal digits, versus the ~16 of Racket’s native double flonums.
Values are opaque: a quad is shuttled across the FFI boundary as 128 bits and is only ever produced or consumed by the operations below. The qf… operations mirror libquadmath’s …q C functions — qfsqrt wraps sqrtq, qffma wraps fmaq, and so on — so libquadmath’s own documentation maps directly onto this API.
> (require quad-fp) > (define a (double-flonum->quad-flonum 1.0)) > (define b (double-flonum->quad-flonum 1e-20)) > (qf= (qf+ a b) a) #f
> (quad-flonum->string (qf* quad-pi quad-pi)) "9.86960440108935861883449099987615081"
The binding loads a native shared library (libquadf) that is compiled from source at install time, so a C toolchain with libquadmath must be present. See the package README for details.
1 Accuracy
The arithmetic operators are compiled as GCC __float128 operations; the elementary and special functions delegate to libquadmath. The exception is qfexp2, which uses powq(2,x) for compatibility with libquadmath versions that do not export exp2q. Accuracy therefore varies across GCC versions. The package’s property tests compare the core operations and functions for which there is a corresponding math/bigfloat operation at 113-bit precision. Their regression bounds are:
qf+, qf-, qf*, qf/, and qfabs must match math/bigfloat bit for bit for the generated finite inputs.
qfsqrt and qffma use the relative-error bound 2^{-110} (roughly 4–8 ulps, depending on the significand). They are bit-exact on recent libquadmath builds, while older implementations may be only faithfully rounded or may double-round.
The tested transcendental functions, including qflgamma, are held to relative error 2^{-106} (roughly 64–128 ulps). qftgamma varies much more across libquadmath versions and uses 2^{-90} (roughly 4–8 million ulps), which still requires about 27 correct decimal digits.
These are conservative regression-test bounds, not universal accuracy guarantees from libquadmath. Functions without a math/bigfloat counterpart, including the Bessel functions, are not covered by those property comparisons. When a reference result is zero, the suite treats the same numeric bound as an absolute rather than relative error limit. Do not rely on bit-for-bit reproducibility of transcendental results across libquadmath versions, especially for qftgamma.
2 Performance
Every operation crosses the Racket/C boundary and allocates a result. That fixed cost is most visible for arithmetic and much less important for expensive transcendental functions. Exact timings depend on the Racket implementation, compiler, CPU, and libquadmath version, so measure on the deployment system:
make bench |
QUADF_BENCH_ITERATIONS=500000 make bench |
The benchmark reports nanoseconds per call, the Racket version, machine type, and iteration count. It measures the untyped quad-fp/quadf layer. The default quad-fp adds Typed Racket contracts at the boundary.
The untyped module uses ffi/unsafe, bypasses the public typed contracts, and exposes low-level representation accessors. Treat it as an optimization interface: use it only after profiling, pass values produced by this package, and do not depend on its extra exports remaining stable.
3 Datatype
type
The public entry point preserves its type information when used from Typed Racket:
#lang typed/racket/base (require quad-fp) (: square (Quad-Flonum -> Quad-Flonum)) (define (square x) (qf* x x)) (quad-flonum->string (square (string->quad-flonum "1.25")))
This usage is compiled and exercised by the package’s typed-client test.
4 Conversion
procedure
(string->quad-flonum s) → Quad?
s : string?
procedure
(quad-flonum->string q [precision]) → string?
q : Quad? precision : (integer-in 1 12000) = 36
Apart from surrounding C-locale whitespace, string->quad-flonum requires the entire input to be consumed as a valid number; malformed strings and strings with trailing nonnumeric text raise exn:fail:contract instead of silently producing the value of a numeric prefix. A syntactically valid value outside binary128’s range is not a parse error: overflow produces signed infinity and underflow produces signed zero.
quad-flonum->string accepts precisions from 1 through 12000 and sizes its output dynamically, so it returns the complete representation even when it is longer than a small fixed buffer. The maximum exceeds the roughly 11,600 significant digits needed by the longest exact finite binary128 expansion. The default of 36 is quad-decimal-dig, the number of digits sufficient to round-trip any finite binary128 value exactly. Textual NaN formatting does not preserve payload or signalling state.
procedure
(quad-flonum->bytes q) → bytes?
q : Quad?
5 IEEE 754 behavior and errors
Arithmetic and mathematical domain errors follow GCC/libquadmath floating-point semantics instead of raising Racket exceptions. For example, division by zero produces signed infinity, invalid operations produce NaN, and overflow or underflow produces infinity, subnormal values, or signed zero as appropriate. The binding does not expose C errno or floating-point exception flags.
NaN is unordered: every comparison involving NaN, including qf=, returns #f. Positive and negative zero compare equal, while qfsignbit? distinguishes them. qfmin and qfmax follow fminq/fmaxq: if exactly one operand is NaN they return the numeric operand. When both operands are zeros with different signs, the returned zero’s sign follows the libquadmath implementation; use qfcopysign when the sign is semantically important.
6 Arithmetic
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
7 Elementary functions
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
8 Rounding
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
(qfnearbyint a) → Quad?
a : Quad?
9 Special functions
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
procedure
a : Quad?
Concurrency note: libquadmath’s lgammaq, which implements qflgamma, writes the process-global C variable signgam. That makes qflgamma unsafe to run concurrently with another qflgamma call or with native code outside the package that calls lgammaq or accesses signgam. Serialize all such access at the application level.
10 Other binary functions
procedure
a : Quad? b : Quad?
procedure
(qfremainder a b) → Quad?
a : Quad? b : Quad?
procedure
(qfcopysign a b) → Quad?
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
(qfnextafter a b) → Quad?
a : Quad? b : Quad?
procedure
a : Quad? b : Quad? c : Quad?
11 Comparison
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
procedure
a : Quad? b : Quad?
12 Classification
procedure
a : Quad?
procedure
(qfinfinite? a) → boolean?
a : Quad?
procedure
a : Quad?
procedure
(qfsignbit? a) → boolean?
a : Quad?
13 Constants
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value