The Python comments this walk toward the underflow threshold and, ultimately, to zero by factors of the radix. The result is a triple – small, smaller, smallest – which reflects how murky matters can be for tiny numbers. These are all simple powers of the radix, so their trailing digits are all zero.
# =============================================
# Tests for underflow behavior.
# =============================================
def tiny_powers_of_B(c):
"""Return tiny values, stepping toward zero by factors of radix or 2.
Starting with a value like C = 1/B**k close to the underflow
threshold, walk down by factors of H = min(1/B, 1/2). Compute
less_tiny, less_tiny, and z differing by factors of H.
Value less_tiny avoids anomalies. Value z typically falls to
zero. The factor H protects against B=1.
Args:
c: 1/B**k, expected to be the constant C or comparable
Returns:
less_tiny: next tiniest positive value, by a factor of H
tiny: tiniest positive value
z: too tiny value, that underflows to zero or a pseudo-zero
Basic 4430-4440
"""
The diagram below captures the action in binary as tiny
reaches the smallest
normal number. It’s the value with full precision
that has the smallest representable
exponent. Value z
, which is half that,
is exactly representable as a subnormal
on an IEEE 754 system. On most other machines, z
flushes
to zero, terminating
the loop.
With subnormal numbers, tiny
reaches the tiniest representable
value, as z
underflows to zero.
It is the first inexact result returned. Can
you see that decimal arithmetic compatible with
IEEE 754 2019 would behave the
same way, with division by 10 at each step?
The termination criterion
if (tiny_delta <= z) or (z + z <= z):
detects several conditions:
z
pins at a tiny nonzero number,
never underflowing to zeroz
reaches zeroz
underflows to a nonzero epsilon value,
named for the Greek letter ε;
epsilons are featured in a section of their own
[Epsilons are introduced elsewhere.
SUGGESTION: Add a verbose
flag to the code, controlling extra output,
such as is given here. Such additions to original Paranoia output are marked
by “***”. The original code reflected the era, restricting output to 80
columns by 24 rows, enabling users to take screen shots.]
tiny = c
z = tiny * H
while True:
less_tiny = tiny
tiny = z
z = z * H
# Terminate if z pins at a nonzero minimal value or
# if z falls to an epsilon or 0.
if tiny <= z or z + z <= z: break
print("***Approaching underflow by powers of the radix:")
print("*** less_tiny = {:.7e}, tiny = {:.7e},"
+ "z = {:.7e}".format(less_tiny, tiny, z))
return less_tiny, tiny, z