Tutorial

Calculations with guaranteed bounds

Use Ball when you need to know how much uncertainty a result carries. It represents every real value between a midpoint minus a radius and that midpoint plus the radius. Arithmetic includes both the input uncertainty and any rounding error in the returned bounds.

From a measurement to a result

Suppose a length is 3, with an uncertainty of one eighth in either direction. Ball(3, Rational(1, 8)) represents that measurement. Squaring it gives an enclosure of every possible area. BallContext(64) chooses 64 bits for the result's midpoint; it does not promise 64 accurate bits in the measurement.

ball_values.mojo Download
"""Carry measurement uncertainty through a calculation."""

from apn_mojo import ArithmeticContext, Ball, BallContext, FloatFormat, Rational, ball


def main() raises:
    var length = Ball(3, Rational(1, 8))
    var area = ball.square(length, context=BallContext(64))
    print("length bounds:", length.lower(), length.upper())
    print("area midpoint and radius:", area.midpoint(), area.radius())
    print("area contains both endpoints:",
          ball.contains(area, Rational(529, 64)),
          ball.contains(area, Rational(625, 64)))
    print("area compared with 8:", ball.compare(area, Ball(8)))
    print("overlapping measurements:", ball.compare(length, Ball(3, Rational(1, 4))))
    print("x - x is exact:", (length - length).is_exact())
    print("division across zero:", (length / Ball(0, 1)).is_indeterminate())

    var root = ball.sqrt(2, context=BallContext(128))
    var rounded = ball.to_float_if_certain(
        root, context=ArithmeticContext(format=FloatFormat.binary64())
    )
    if rounded:
        print("certified sqrt(2):", rounded.value())
    else:
        raise Error("The enclosure did not determine one rounded value.")

Run from the repository root pixi run mojo run -I src docs/examples/ball_values.mojo

Output

length bounds: 2.875 3.125
area midpoint and radius: 9.0 0.765625
area contains both endpoints: True True
area compared with 8: BallOrder.greater
overlapping measurements: BallOrder.overlap
x - x is exact: False
division across zero: True
certified sqrt(2): 1.4142135623730951

midpoint() and radius() expose the stored description. lower() and upper() give outward-rounded endpoints; lower_rational() and upper_rational() give the exact endpoints as fractions. The area contains the squares of both extreme lengths, and may extend farther to keep the enclosure guarantee.

An exact input can also need a radius: Ball(Rational(1, 3)) encloses one third, which has no finite binary representation. Ball("0.1") encloses exactly one tenth. Constructing from an already rounded Float instead encloses that stored value; it cannot recover earlier rounding error.

Decide what the bounds establish

Balls have no ordinary comparison operators. ball.compare(a, b) returns less, equal, greater, overlap, or undefined. In the example, every possible area exceeds 8, so the result is BallOrder.greater. Overlapping measurements do not establish equality. Use contains to test a number, contains_ball to test an entire interval, and overlaps to test whether two intervals meet.

An indeterminate ball has no usable enclosure. For example, division by an interval containing zero includes an undefined calculation. Check is_indeterminate() before trying to inspect finite endpoints. An unbounded ball instead represents every real number; is_finite() distinguishes both cases from a finite interval.

Understand why bounds widen

Each operation considers its operand intervals independently. The library does not remember that two occurrences came from the same measurement. For an uncertain x, x - x therefore need not be the exact zero, even though the algebraic expression is zero. Simplifying the expression before evaluating it can give much tighter bounds.

Increasing midpoint precision reduces rounding error. It cannot remove measurement uncertainty or restore relationships lost between intermediate intervals. To benefit from a higher precision, recompute from the original exact inputs or measurements. Merely widening the format of an existing result does not narrow its uncertainty.

Extract a result when the rounding is certain

ball.to_float_if_certain returns an Optional Float. It succeeds only when every point in the enclosure rounds to the same result with the requested context and status. In the example, a 128-bit enclosure of sqrt(2) is narrow enough to determine its 53-bit rounded value. None means the enclosure does not establish one result under those rules.

If the uncertainty comes from rounding, retry the whole calculation with more working bits and a finite retry budget. If it comes from the input, you may need a better measurement or a less demanding output precision. The precision guide demonstrates a bounded retry and a separate check for reliable decimal digits.

Rectangles in the complex plane

ComplexBall pairs real and imaginary balls into a rectangle. Use it for uncertainty in complex arithmetic; complex_ball.abs returns a real Ball for the magnitude. Both component intervals participate in the calculation.

complex_ball_values.mojo Download
"""Rectangular uncertainty and a certified complex result."""

from apn_mojo import (
    ArithmeticContext, Ball, BallContext, ComplexBall, ComplexContext,
    FloatFormat, Rational, ball, complex_ball,
)


def main() raises:
    var measured = ComplexBall(Ball(3, Rational(1, 8)), Ball(4, Rational(1, 8)))
    var magnitude = complex_ball.abs(measured, context=BallContext(64))
    print("magnitude contains 5:", ball.contains(magnitude, 5))
    print("magnitude is exact:", magnitude.is_exact())

    var root = complex_ball.sqrt(ComplexBall(3, 4), context=BallContext(128))
    print("root contains 2 + i:", complex_ball.contains(root, ComplexBall(2, 1)))
    var rounded = complex_ball.to_complex_if_certain(
        root, context=ComplexContext(ArithmeticContext(format=FloatFormat.binary64()))
    )
    if rounded:
        print("certified root:", rounded.value())
    else:
        raise Error("The rectangle did not determine one rounded complex value.")

    var across_cut = ComplexBall(Ball(-2), Ball(0, Rational(1, 8)))
    var logarithm = complex_ball.log(across_cut, context=BallContext(64))
    print("log across the cut spans both signs:", ball.contains_zero(logarithm.imag()))

Run from the repository root pixi run mojo run -I src docs/examples/complex_ball_values.mojo

Output

magnitude contains 5: True
magnitude is exact: False
root contains 2 + i: True
certified root: Complex(2.0, 1.0)
log across the cut spans both signs: True

to_complex_if_certain requires a definite rounded value for both parts. Functions use their principal branches. When a rectangle crosses a branch cut, its result covers both sides: the logarithm around a negative real number can have an imaginary interval spanning from negative to positive pi. Increasing precision does not remove that jump. A rectangle containing a singularity, such as zero for log, gives an indeterminate result.

Work with many intervals

Batch[Ball] and Batch[ComplexBall] support shapes, selections, and mapping. Use functions such as batch.add and batch.exp, or pass a family function to vmap or lift. Ball batches have no arithmetic operators or batch JSON. Real Ball batches support min and max; both families support cumsum and cumprod. Use lift for other folds, preserving operand order unless your function is associative.

See the Ball reference and ComplexBall reference for set operations, special functions, serialization, and the full contracts.