from __future__ import annotations
import random
from pathlib import Path
from mpmath import mp, mpf, floor as mp_floor, exp, ln, sqrt, sin
SCALE = 28
SCALE_POW = 10 ** SCALE
I128_MAX = (1 << 127) - 1
RANGE_LIMIT = mpf(I128_MAX) / mpf(SCALE_POW) * mpf("0.95")
mp.dps = 2 * SCALE + 80
OUT_DIR = Path(__file__).resolve().parent.parent / "tests" / "oracle_golden"
PER_FUNC = 64
def floor_and_class(value: mpf) -> tuple[int, str] | None:
scaled = value * mpf(SCALE_POW)
if abs(scaled) >= mpf(I128_MAX):
return None
floor_int = int(mp_floor(scaled))
frac = scaled - mpf(floor_int)
half = mpf("0.5")
if frac == 0:
cls = "Z"
elif frac < half:
cls = "L"
elif frac == half:
cls = "E"
else:
cls = "G"
return floor_int, cls
def to_raw(x: mpf) -> int:
scaled = x * mpf(SCALE_POW)
floor_int = int(mp_floor(scaled))
frac = scaled - mpf(floor_int)
return floor_int + 1 if frac >= mpf("0.5") else floor_int
def gen_inputs(name: str, rng: random.Random) -> list[mpf]:
xs: list[mpf] = []
if name == "exp":
hi = float(ln(RANGE_LIMIT))
xs += [mpf(rng.uniform(-hi, hi)) for _ in range(PER_FUNC - 4)]
xs += [mpf(0), mpf("0.5"), mpf(-1), mpf(1)]
elif name == "ln":
lo, hi = -20.0, float(ln(RANGE_LIMIT))
xs += [exp(mpf(rng.uniform(lo, hi))) for _ in range(PER_FUNC - 3)]
xs += [mpf(1), mpf(2), mpf("0.5")]
elif name == "sqrt":
hi = float(RANGE_LIMIT)
xs += [mpf(rng.uniform(0, hi)) for _ in range(PER_FUNC - 4)]
xs += [mpf(4), mpf(9), mpf("2.25"), mpf(0)]
elif name == "sin":
xs += [mpf(rng.uniform(-20.0, 20.0)) for _ in range(PER_FUNC - 3)]
xs += [mpf(0), mpf(1), mpf(-1)]
else:
raise ValueError(name)
return xs
FUNCS = {"exp": exp, "ln": ln, "sqrt": sqrt, "sin": sin}
def main() -> None:
OUT_DIR.mkdir(parents=True, exist_ok=True)
for name, fn in FUNCS.items():
rng = random.Random(f"gmath-decimal-golden-{name}-s{SCALE}")
rows: list[tuple[int, int, str]] = []
seen: set[int] = set()
for x in gen_inputs(name, rng):
raw = to_raw(x)
if raw in seen:
continue
seen.add(raw)
xv = mpf(raw) / mpf(SCALE_POW)
if name in ("ln", "sqrt") and xv < 0:
continue
if name == "ln" and xv == 0:
continue
res = floor_and_class(fn(xv))
if res is None:
continue floor_raw, cls = res
rows.append((raw, floor_raw, cls))
rows.sort()
path = OUT_DIR / f"{name}_s{SCALE}.txt"
with path.open("w") as f:
f.write("# gMath decimal contract golden table (oracle #1)\n")
f.write("# generated by scripts/gen_decimal_golden.py\n")
f.write(f"# function={name} scale={SCALE} guard_dps={mp.dps}\n")
f.write("# each line: <input_raw>\\t<floor_raw>\\t<cls>\n")
f.write("# value x = input_raw / 10**scale; "
"floor_raw = floor(f(x)*10**scale) toward -inf;\n")
f.write("# cls: Z exact, L below-half, E half-tie, G above-half.\n")
for raw, floor_raw, cls in rows:
f.write(f"{raw}\t{floor_raw}\t{cls}\n")
print(f"{path.name}: {len(rows)} rows")
if __name__ == "__main__":
main()