Not-quite-transcendental functions
nqt_math.hpp provides fast, invertible approximations to base-2
logarithms and powers of two. These functions are useful when an
application needs approximately logarithmic spacing and an
inexpensive, but exact, inverse, rather than the exact transcendental
functions.
The implementation is based on the underlying structure of floating point numbers:
where m is the mantissa and e is the integer exponent. The
NQT functions approximate the mantissa contribution while preserving
the useful properties of logarithmic spacing and invertibility.
Background and references
The NQT approach and its motivation are described in:
Jonah M. Miller, Joshua C. Dolence, and Daniel Holladay,
Not-Quite Transcendental Functions and their Applications, arXiv:2206.08957.Peter C. Hammond, Jacob M. Fields, Jonah M. Miller, and Brandon L. Barker,
Not-quite-transcendental Functions for Logarithmic Interpolation of Tabulated Data, The Astrophysical Journal Supplement Series 277, 65 (2025), doi:10.3847/1538-4365/adbbd4, also available as arXiv:2501.05410.
The first paper introduces the general not-quite-transcendental construction;
the second develops the higher-order form used by NQT::O2 for logarithmic
interpolation of tabulated data.
API organization
The public API is organized first by approximation order and then by implementation strategy:
PortsOfCall::NQT::O1contains the first-order, piecewise-linear approximation.PortsOfCall::NQT::O2contains the second-order approximation, which provides a smoother derivative.Portableimplementations usefrexpandldexpand do not inspect the binary representation of adouble.Aliasedimplementations use the IEEE-754 binary representation through theFP64LEhelpers and are intended for platforms with the expected 64-bit floating-point representation. These methods are faster than thePortableimplementations.
Each implementation namespace provides lg and pow2:
#include <ports-of-call/nqt_math.hpp>
const double y = PortsOfCall::NQT::O2::Portable::lg(x);
const double x_round_trip = PortsOfCall::NQT::O2::Portable::pow2(y);
The first-order namespaces also provide matched asinh and sinh
approximations:
const double y = PortsOfCall::NQT::O1::Aliased::sinh(x);
const double x_round_trip = PortsOfCall::NQT::O1::Aliased::asinh(y);
For every implementation, lg requires a finite positive argument. The
pow2 functions accept finite arguments in the range [-1022, 1024].
The aliased lg functions additionally reject subnormal values because the
integer representation technique is not valid for them.
The functions are decorated with the portability macros and can therefore be
called from portable host/device code. Portable describes the numerical
implementation strategy; it does not mean that the Aliased functions are
host-only.