Functional Weave
Code in Python

math.round-float@1.0.0

impl/python.py

1,957 bytes · the Python implementation · view raw

import math

POW10 = [1.0, 10.0, 100.0, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12]

# 2^52: at or above this every double is a whole number, so a scaled value
# this large has no digits left to round.
_INTEGRAL = 4503599627370496.0
# Veltkamp's splitting constant, 2^27 + 1.
_SPLIT = 134217729.0


def _product_error(a: float, b: float, p: float) -> float:
    """a * b - fl(a * b), exactly (Dekker's TwoProduct), with only * and -."""
    ca = _SPLIT * a
    ah = ca - (ca - a)
    al = a - ah
    cb = _SPLIT * b
    bh = cb - (cb - b)
    bl = b - bh
    return ((ah * bh - p) + ah * bl + al * bh) + al * bl


def round_float(value: float, decimals: int) -> float:
    """Round half away from zero to ``decimals`` places, deciding on the exact
    value of the double.

    2.675 is stored as 2.674999..., so it rounds to 2.67. Python's round() is
    half-even (round(0.125, 2) == 0.12); this gives 0.13. The product's
    rounding error is recovered exactly, so the tie test is on the true value,
    with the same operations in the same order as TypeScript and Rust.
    """
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
        raise TypeError("value must be a finite number, received %r" % (value,))
    if isinstance(decimals, bool) or not isinstance(decimals, int) or decimals < 0 or decimals > 12:
        raise ValueError("decimals must be a whole number from 0 to 12, received %r" % (decimals,))
    scale = POW10[decimals]
    a = abs(float(value))
    y = a * scale
    if y >= _INTEGRAL:
        return float(value) + 0.0
    r = float(math.floor(y))
    # y - r is exact, and so is subtracting a half from it; adding the
    # product's error cannot change the sign, only settle a tie.
    above = (y - r - 0.5) + _product_error(a, scale, y)
    if above >= 0:
        r += 1.0
    out = r / scale
    # + 0.0 turns -0.0 into 0.0.
    return (-out if value < 0 else out) + 0.0