The following tool lets you inspect the computational flow of a floating point expression, seeing where rounding occurs, when exceptions are triggered, when precision may be lost, when special values propagate, when error accumulates, and other floating point headaches.
[fpinspect]# ./fpinspect "sqrt(45.0*e+phi)/pi"
Exception: 0 (1 roundings) INEXACT (45.000000 * e)
Trace (1 operations) MUL
Exception: 0 (1 roundings) INEXACT phi
Trace (1 operations) MUL
Exception: 0 (2 roundings) INEXACT ((45.000000 * e) + phi)
Exception: 1 (2 roundings) INEXACT ((45.000000 * e) + phi)
Trace (2 operations) MUL ADD
Exception: 0 (3 roundings) INEXACT sqrt(((45.000000 * e) + phi))
Exception: 1 (3 roundings) INEXACT sqrt(((45.000000 * e) + phi))
Exception: 2 (3 roundings) INEXACT sqrt(((45.000000 * e) + phi))
Trace (3 operations) MUL ADD ADD
Exception: 0 (3 roundings) INEXACT pi
Exception: 1 (3 roundings) INEXACT pi
Exception: 2 (3 roundings) INEXACT pi
Trace (3 operations) MUL ADD ADD
Exception: 0 (4 roundings) INEXACT (sqrt(((45.000000 * e) + phi)) / pi)
Exception: 1 (4 roundings) INEXACT (sqrt(((45.000000 * e) + phi)) / pi)
Exception: 2 (4 roundings) INEXACT (sqrt(((45.000000 * e) + phi)) / pi)
Exception: 3 (4 roundings) INEXACT (sqrt(((45.000000 * e) + phi)) / pi)
Trace (4 operations) MUL ADD ADD DIV
(sqrt(((45.000000 * e) + phi)) / pi)
ans: 3.54370117187500
err: 0.00000126456894
As you can see, the expression sqrt(45.0*e+phi)/pi
produces a lot of output,
each empty-line-separated region is a subexpression which triggered an exception,
in this case because 45 * e
is an inexact value, the inexact exception is
presented first. Here you can see that such an expression involved 1 operations
,
total and in this case the operation is just a MUL
. We can also see that the
resulting expression, because it's inexact, incurred one rounding.
Following down the exception list, we can see that the exception propagated
to phi
in a MUL
(which is also an inexact value), and continued, with each
new inexact subexpression resulting in several roundings. Since kernels like
sqrt
might themselves use operations like add
, we also see the final group
of exceptions contains an additional ADD
in it's trace.
The final result of the expression is given in ans:
and below that you will
find the accumulative error err:
of evaluating that expression, in this case
this function is exact to five mantissa digits of precision, out of a total of
seven, which means this expression has ~0.71 ULP of error.
Run the program with no expression or -h
to see the options.
Here's some constants and functions available for use in expressions.
- e
- pi
- phi
- floor
- ceil
- trunc
- sqrt
- abs
- min
- max
- copysign
This program implements IEEE-754 floating point completely in software, emulating all rounding modes, exceptions, and tininess detection methods which can be configured when evaluating an expression. With exception to transcendental functions, all floating point computation is also accurate to <= 1 ULP of error.
Currently there is support for 32-bit single-precision floating-point
float32.{h,c}
and 64-bit double-precision floating-point float64.{h,c}
, as
double-precision is necessary for 32-bit single-precision kernels
kernel32.{h,c}
to produce correctly rounded and truncated results
to <= 1 ULP of error.
64-bit double-precision floating-point makes use of 128-bit modular arithmetic
implemented in uint128.{h,c}
Accumulative error accounting is handled by real32.{h,c}
and real64.{h,c}
for single-precision and double-precision floating-point, respectively.
NOTE:
There are currently no 64-bit kernels, as that would require either 80-bit extended-precision floating-point, or 128-bit quadruple-precision floating-point to be implemented in software to have the precision necessary to produce correctly rounded and truncated results to <= 1 ULP of error.