codexinterval-rounding-enclosures.py
interval-rounding-enclosures.py
"""Decimal toy outward rounding and sign-changing interval multiplication.
Save interval-rounding-enclosures.png to the current working directory.
Fraction supplies every enclosure and endpoint; floats only position the drawing.
The decimal grid is illustrative and does not simulate binary floating point.
Tested with Python 3.14.4, Matplotlib 3.10.7 and NumPy 2.3.5.
"""
from fractions import Fraction as Q
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def grid_down(value, step):
return (value // step) * step
def grid_up(value, step):
return -((-value) // step) * step
def verify():
value, step = Q(1, 3), Q(1, 1000)
down, up = grid_down(value, step), grid_up(value, step)
assert (down, up) == (Q(333, 1000), Q(334, 1000))
nearest = down if value - down < up - value else up
assert nearest == down and nearest != value
# Check both rounding directions on signed values and exact grid points.
for value in (Q(-1, 3), Q(0), Q(1, 3), Q(334, 1000)):
low, high = grid_down(value, step), grid_up(value, step)
assert low <= value <= high and high - low <= step
products = [x * y for x in (Q(-2), Q(3)) for y in (Q(-4), Q(1))]
assert products == [Q(8), Q(-2), Q(-12), Q(3)]
assert (min(products), max(products)) == (Q(-12), Q(8))
def main():
verify()
plt.rcParams.update({"font.size": 11, "axes.titlesize": 12})
fig = plt.figure(figsize=(10, 6.4), dpi=100, facecolor="white")
grid = fig.add_gridspec(2, 2, height_ratios=(1, 1.2), width_ratios=(1, 1.45), hspace=0.7, wspace=0.48)
rounding = fig.add_subplot(grid[0, :])
corners = fig.add_subplot(grid[1, 0])
bars = fig.add_subplot(grid[1, 1])
fig.suptitle("Two ways an alleged enclosure can miss valid values", fontsize=16, y=0.97)
value, step = Q(1, 3), Q(1, 1000)
down, up = grid_down(value, step), grid_up(value, step)
# Coordinates in thousandths above 0.333 avoid unreadable tiny tick offsets.
def pos(number):
return float((number - down) / step)
rounding.plot([pos(down), pos(up)], [0, 0], color="#3479a7", lw=7, solid_capstyle="butt")
rounding.scatter([pos(down), pos(up)], [0, 0], color="#3479a7", s=55, zorder=3)
rounding.scatter([pos(down)], [1], marker="x", s=100, color="#bb3e28", lw=2.5, zorder=3)
rounding.axvline(pos(value), color="#222222", ls="--", lw=1.5)
rounding.text(pos(value), 1.43, "Exact 1/3", ha="center")
rounding.text(0.53, 1, "Nearest: [0.333, 0.333] misses 1/3", va="center", color="#bb3e28")
rounding.text(0.53, 0.24, "Outward: [0.333, 0.334] encloses 1/3", color="#3479a7")
rounding.set(title="Illustrative decimal grid: step 0.001 (not binary rounding)",
xlim=(-0.06, 1.1), ylim=(-0.4, 1.8), yticks=(),
xticks=(0, pos(value), 1), xticklabels=("0.333", "1/3", "0.334"))
x, y = (Q(-2), Q(3)), (Q(-4), Q(1))
corners.add_patch(Rectangle((float(x[0]), float(y[0])), float(x[1] - x[0]), float(y[1] - y[0]),
facecolor="#edf3f7", edgecolor="#3479a7", lw=1.5))
corners.axhline(0, color="#999999", lw=0.7)
corners.axvline(0, color="#999999", lw=0.7)
products = []
for a in x:
for b in y:
p = a * b
products.append(p)
color = "#bb3e28" if p < 0 else "#3479a7"
corners.scatter([float(a)], [float(b)], color=color, s=40, zorder=3)
corners.annotate(str(p), (float(a), float(b)),
xytext=(7 if a == x[0] else -7, 8 if b == y[0] else -17),
textcoords="offset points", ha="left" if a == x[0] else "right",
color=color, fontsize=12, weight="bold")
corners.set(title="Four corner products xy", xlim=(-2.7, 3.7), ylim=(-4.7, 1.7),
xticks=(-2, 0, 3), yticks=(-4, 0, 1), xlabel="x ∈ [−2, 3]", ylabel="y ∈ [−4, 1]")
wrong = sorted((x[0] * y[0], x[1] * y[1]))
correct = min(products), max(products)
for row, bounds, color in ((1, wrong, "#bb3e28"), (0, correct, "#3479a7")):
bars.plot([float(p) for p in bounds], [row, row], lw=8, color=color, solid_capstyle="butt")
bars.scatter([float(p) for p in bounds], [row, row], s=40, color=color, zorder=3)
bars.text(-12, 1.44, "Same-side shortcut: [3, 8]", color="#bb3e28")
bars.text(-12, 0.36, "All four products: [−12, 8]", color="#3479a7")
bars.scatter([float(p) for p in products], [-0.48] * 4, marker="|", s=140, color="#222222")
bars.text(-12, -0.78, "Ticks: −12, −2, 3, 8", fontsize=10)
bars.set(title="Correct interval product: [−12, 8]", xlim=(-13, 9), ylim=(-1, 1.8),
yticks=(), xticks=(-12, -2, 0, 3, 8), xlabel="Possible product value")
for ax in (rounding, corners, bars):
ax.spines[["top", "right"]].set_visible(False)
for ax in (rounding, bars):
ax.spines["left"].set_visible(False)
fig.subplots_adjust(left=0.09, right=0.96, bottom=0.1, top=0.84)
fig.savefig(Path.cwd() / "interval-rounding-enclosures.png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()