1,503 bytes · the Python implementation · view raw
import math
from typing import Sequence, Tuple
def linear_scale(domain: Sequence[float], range: Sequence[float], value: float, clamp: bool) -> float:
"""Where value lands on the range, as a straight-line map from the domain. Outside the domain the line carries on, unless clamp stops it at the range's ends. """
d0, d1 = pair(domain, "domain")
r0, r1 = pair(range, "range")
t = normalise(d0, d1, value, "domain")
if clamp:
t = max(0.0, min(1.0, t))
return round6(interpolate(r0, r1, t))
# The helpers below are shared with invert_linear, which is the same line read# the other way; they are not part of the group's contract.def pair(values: Sequence[float], what: str) -> Tuple[float, float]:
if len(values) != 2:
raise ValueError(f"{what} must have exactly 2 values, [from, to]; got {len(values)}")
return float(values[0]), float(values[1])
def normalise(start: float, end: float, value: float, what: str) -> float:
# Equal ends would divide by zero, which Python raises as a bare# ZeroDivisionError that does not say which end is wrong.if start == end:
raise ValueError(f"{what} has no width: both ends are {start}")
return (value - start) / (end - start)
def interpolate(start: float, end: float, t: float) -> float:
return start + t * (end - start)
def round6(x: float) -> float:
"""Half up to 6 decimal places, the same way in every language."""return math.floor(x * 1e6 + 0.5) / 1e6