Skip to content

Commit acfaeb3

Browse files
author
TNFR AI Agent
committed
Preserve exact NumPy scalar bounds across platforms
Mixed longdouble and Fraction comparisons rejected valid closed endpoints and raised TypeError for out-of-domain inputs. Compare supported original floating sources and bounds through their exact integer ratios, reusing the already represented ratio for native floats. Accepted runtime values still use binary64; precision is retained only for the admission decision. Add 14 portable NumPy/rational endpoint, outside-boundary and reversed-bound controls, while retaining the genuine extended-precision Linux regression. Focused checks pass locally; the exact-commit Linux matrix verifies the platform-specific case before release publication.
1 parent bd77ca2 commit acfaeb3

3 files changed

Lines changed: 70 additions & 7 deletions

File tree

‎docs/API_CONTRACTS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,9 @@ unchecked call does not acquire that certificate.
475475
Closed scalar configuration intervals use `bounded_represented_real` from
476476
`_exact_time.py` to check the original admitted source against their bounds.
477477
An exact value outside the interval cannot enter by rounding to an endpoint;
478-
accepted values retain the consumer's binary64 model. This boundary applies
478+
floating sources and bounds exposing exact integer ratios use those ratios
479+
for comparison, retaining precision beyond binary64 when available.
480+
Accepted values retain the consumer's binary64 model. This boundary applies
479481
to selector/adaptation controls, stability fractions and active glyph factors.
480482
It does not prohibit rounded arithmetic already declared by a numerical model.
481483

‎src/tnfr/_exact_time.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,26 @@ def bounded_represented_real(
7676
"""
7777
result = finite_represented_real(value, label)
7878

79-
def ordered_source(raw: Any) -> Any:
79+
def ordered_source(raw: Any, represented: Fraction) -> Any:
8080
if isinstance(raw, Rational):
8181
return Fraction(int(raw.numerator), int(raw.denominator))
82+
if type(raw) is float:
83+
return represented
84+
ratio = getattr(raw, "as_integer_ratio", None)
85+
if ratio is not None:
86+
# Extended floating scalars need not compare directly with Fraction.
87+
# Their source ratio preserves precision beyond the runtime float.
88+
numerator, denominator = ratio()
89+
return Fraction(int(numerator), int(denominator))
8290
return raw
8391

84-
source = ordered_source(value)
92+
source = ordered_source(value, result[1])
8593
if minimum is not None:
86-
finite_represented_real(minimum, f"{label} lower bound")
87-
minimum = ordered_source(minimum)
94+
_, exact_minimum = finite_represented_real(minimum, f"{label} lower bound")
95+
minimum = ordered_source(minimum, exact_minimum)
8896
if maximum is not None:
89-
finite_represented_real(maximum, f"{label} upper bound")
90-
maximum = ordered_source(maximum)
97+
_, exact_maximum = finite_represented_real(maximum, f"{label} upper bound")
98+
maximum = ordered_source(maximum, exact_maximum)
9199
if minimum is not None and maximum is not None and minimum > maximum:
92100
raise ValueError(f"{label} lower bound must not exceed its upper bound")
93101
if minimum is not None and source < minimum:

‎tests/test_bounded_scalar_admission.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,59 @@ def test_fixed_width_fraction_components_do_not_overflow_original_domain_checks(
6565
bounded_represented_real(value, "ratio", minimum=bound, maximum=value)
6666

6767

68+
@pytest.mark.parametrize(
69+
"value,minimum,maximum",
70+
[
71+
(np.longdouble(0), Fraction(0), Fraction(1)),
72+
(np.longdouble(1), Fraction(0), Fraction(1)),
73+
(np.longdouble(0.5), Fraction(1, 2), Fraction(1, 2)),
74+
(Fraction(1, 2), np.longdouble(0.5), np.longdouble(0.5)),
75+
(np.longdouble(0.5), np.longdouble(0), Fraction(1)),
76+
(np.longdouble(0.5), Fraction(0), np.longdouble(1)),
77+
(0.5, np.longdouble(0.5), Fraction(1, 2)),
78+
],
79+
)
80+
def test_numpy_real_and_rational_bounds_admit_exact_endpoints(value, minimum, maximum):
81+
actual, exact = bounded_represented_real(
82+
value, "ratio", minimum=minimum, maximum=maximum
83+
)
84+
assert actual == float(value)
85+
assert exact == Fraction.from_float(actual)
86+
87+
88+
@pytest.mark.parametrize(
89+
"value,minimum,maximum",
90+
[
91+
(np.longdouble(1), 0, Fraction(1) - Fraction(1, 2**100)),
92+
(np.longdouble(1), _BEYOND_ONE, 2),
93+
(_BEYOND_ONE, 0, np.longdouble(1)),
94+
(Fraction(1) - Fraction(1, 2**100), np.longdouble(1), 2),
95+
(np.longdouble(-1), Fraction(-1) + Fraction(1, 2**100), 0),
96+
],
97+
)
98+
def test_numpy_real_and_rational_bounds_reject_exact_outside_values(
99+
value, minimum, maximum
100+
):
101+
# The source or bound differs from its adjacent endpoint below binary64
102+
# resolution, including on platforms where longdouble itself is binary64.
103+
with pytest.raises(ValueError, match="ratio must be"):
104+
bounded_represented_real(value, "ratio", minimum=minimum, maximum=maximum)
105+
106+
107+
@pytest.mark.parametrize(
108+
"minimum,maximum",
109+
[
110+
(np.longdouble(1), Fraction(1) - Fraction(1, 2**100)),
111+
(_BEYOND_ONE, np.longdouble(1)),
112+
],
113+
)
114+
def test_numpy_real_and_rational_bounds_reject_exact_reversed_interval(
115+
minimum, maximum
116+
):
117+
with pytest.raises(ValueError, match="lower bound"):
118+
bounded_represented_real(1, "ratio", minimum=minimum, maximum=maximum)
119+
120+
68121
def test_higher_precision_real_is_not_rounded_into_the_unit_interval():
69122
source = np.nextafter(np.longdouble(1), np.longdouble(2))
70123
if source == np.longdouble(float(source)):

0 commit comments

Comments
 (0)