#!/usr/bin/env python3 """Reproduce Time Calculator decimal-hours v1. Python 3.9+, standard library. Run: python3 recompute.py --output ./recomputed CSV/JSON and uncompressed corpus are deterministic; no network, clock, floats or private inputs. Compressed bytes can vary with the Python/zlib version. Own code and generated data: CC0-1.0. Third-party sources retain their rights. """ import argparse import csv from fractions import Fraction import gzip import hashlib import io import json from pathlib import Path def nearest_positive(value): """Nearest integer; exact halves round upward. Inputs must be nonnegative.""" if value < 0: raise ValueError("Only nonnegative durations belong to this corpus") return (value.numerator * 2 + value.denominator) // (2 * value.denominator) def decimal(units, places): scale = 10 ** places return f"{units // scale}.{units % scale:0{places}d}" def generate(destination): destination.mkdir(parents=True, exist_ok=True) table = io.StringIO(newline="") chart = csv.writer(table, lineterminator="\n") chart.writerow(["minutes", "exact_hours_fraction", "hours_2dp", "hours_3dp", "hours_4dp"]) for minute in range(61): exact = Fraction(minute, 60) chart.writerow([minute, str(exact), *[decimal(nearest_positive(exact * 10 ** p), p) for p in (2, 3, 4)]]) (destination / "minutes-chart.csv").write_text(table.getvalue(), encoding="utf-8") corpus = io.StringIO(newline="") rows = csv.writer(corpus, lineterminator="\n") rows.writerow(["input_seconds", "decimal_places", "rounded_hours", "represented_seconds_fraction", "signed_error_seconds_fraction", "recovered_seconds", "round_trip_error_seconds"]) results = [] for places in (2, 3, 4): scale = 10 ** places maximum = Fraction(0) first_maximum = None changed = max_round_trip = exact_count = 0 for seconds in range(86401): units = nearest_positive(Fraction(seconds * scale, 3600)) represented = Fraction(units * 3600, scale) error = represented - seconds recovered = nearest_positive(represented) delta = recovered - seconds if abs(error) > maximum: maximum, first_maximum = abs(error), seconds changed += delta != 0 exact_count += error == 0 max_round_trip = max(max_round_trip, abs(delta)) rows.writerow([seconds, places, decimal(units, places), str(represented), str(error), recovered, delta]) results.append({"decimal_places": places, "tested_durations": 86401, "quantum_seconds_fraction": str(Fraction(3600, scale)), "max_absolute_display_error_seconds_fraction": str(maximum), "first_max_error_input_seconds": first_maximum, "exact_display_count": exact_count, "changed_round_trips": changed, "max_absolute_round_trip_error_seconds": max_round_trip}) compressed = io.BytesIO() with gzip.GzipFile(filename="", mode="wb", fileobj=compressed, compresslevel=9, mtime=0) as archive: archive.write(corpus.getvalue().encode("utf-8")) (destination / "precision-corpus.csv.gz").write_bytes(compressed.getvalue()) summary = {"schema_version": "decimal-hours-precision/v1", "version": "1.0.0", "published_date": "2026-10-29", "owner": "Time Calculator", "canonical_page": "https://time-calculator.dev/minutes-to-decimal-chart", "scope": "Every integer second from 0 through 86400 inclusive; nonnegative elapsed durations, not clock readings", "rounding": "Nearest, exact halves upward (half away from zero on this nonnegative corpus); recover to nearest whole second using the same rule", "arithmetic": "Python integers and fractions.Fraction; no binary floating-point in the reference calculation", "total_comparisons": 259203, "results": results, "aggregation_examples": [ {"entries": 3, "seconds_per_entry": 1200, "decimal_places": 2, "sum_of_rounded_hours": "0.99", "rounded_sum_hours": "1.00", "difference_seconds": -36}, {"entries": 30, "seconds_per_entry": 60, "decimal_places": 4, "sum_of_rounded_hours": "0.5010", "rounded_sum_hours": "0.5000", "difference_seconds_fraction": "18/5"}], "limitations": ["This is a deterministic arithmetic enumeration, not an empirical survey or a benchmark of third-party software.", "Excludes negative and subsecond inputs, durations beyond 24 hours, parsing, floating-point implementations, timezone, daylight-saving and payroll rounding policies.", "Zero changed four-place round trips is only a single-value nearest-second result on the stated corpus; it does not imply exact decimal values or exact sums."], "reuse": "CC0-1.0 for original code and generated data; external publications retain their rights"} (destination / "results.json").write_text(json.dumps(summary, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") names = ["minutes-chart.csv", "precision-corpus.csv.gz", "results.json"] (destination / "GENERATED-SHA256SUMS.txt").write_text("".join(f"{hashlib.sha256((destination / name).read_bytes()).hexdigest()} {name}\n" for name in names), encoding="utf-8") return summary if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, required=True) print(json.dumps(generate(parser.parse_args().output), indent=2))