Skip to content

Commit 4abfbd0

Browse files
Ravenwaterclaude
andauthored
refactor(elreal): consolidate exact-dyadic oracle helpers into one shared header (#1035) (#1056)
The exact-dyadic oracle helpers (exact_real / exact_block / exact_blocks / exact_value / exact_series_sum / approx / check_zero_overlap), built on dyadic_exact.hpp (the #1022 oracle work), had been copy-pasted into FIVE places that had already drifted apart: addition.cpp and add_renormalization.cpp (inline), exact_value_oracle.cpp (as exact_zbcl), summation/summation_oracle.hpp (z.take(32)), and arithmetic/arithmetic_oracle.hpp (z.take(64), added after the issue with a "consolidation tracked by #1035" note). Keeping the subtle digits<=53 vs wide-cfloat bit path in sync by hand was error-prone, and the 32-vs-64 window split was exactly the drift #1035 warned about. Promote them to a single source of truth: include/sw/universal/verification/elreal_oracle.hpp namespace sw::universal::elreal_oracle All helpers are templates (implicitly inline), so multi-TU inclusion is ODR-safe. The window is unified to ZBCL_EXACT_WINDOW = 32 (the #1022 value). This is behaviour-preserving: a ZBCL<double> near 1.0 saturates at ~19-20 non-overlapping components against the 2^-1022 floor, so take(32) == take(64) for every test value; the arithmetic suite's former 64-window captured nothing extra. - New: verification/elreal_oracle.hpp (superset of all five copies). - Deleted: summation/summation_oracle.hpp, arithmetic/arithmetic_oracle.hpp. - 17 elreal test TUs now include the shared header; the `namespace est = ...` aliases retarget to elreal_oracle (call sites unchanged). addition.cpp, add_renormalization.cpp, and exact_value_oracle.cpp drop their inline copies for a using-directive; exact_value_oracle.cpp's exact_zbcl call sites become exact_value. exact_value_oracle.cpp keeps its EFT-specific oracle code (dval / block_ulp / EFT checks) local. Pure refactor, no behaviour change. Verified: el_arith_*, el_sum_*, el_math_* (20 targets) build and pass with BOTH gcc and clang. ASCII clean. Resolves #1035 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2f782dc commit 4abfbd0

19 files changed

Lines changed: 173 additions & 346 deletions

elastic/elreal/arithmetic/add_renormalization.cpp

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
#include <vector>
2121

2222
#include <universal/number/elreal/elreal.hpp>
23-
#include <universal/verification/dyadic_exact.hpp>
23+
#include <universal/verification/elreal_oracle.hpp>
2424
#include <universal/verification/test_suite.hpp>
2525

2626
namespace {
2727

2828
using sw::universal::block;
2929
using sw::universal::ZBCL;
30+
// exact_block / exact_value: the shared exact-dyadic oracle (#1035).
31+
using namespace sw::universal::elreal_oracle;
3032

3133
// 0-overlap check + descending-exponent sanity over the first n blocks.
3234
template <typename FpType>
@@ -44,25 +46,6 @@ int check_canonical(const ZBCL<FpType>& z, std::size_t n, const std::string& tag
4446
return fails;
4547
}
4648

47-
// Compare the exact value of an add() result to a dyadic reference.
48-
template <typename FpType>
49-
sw::universal::dyadic exact_block(const block<FpType>& b) {
50-
using namespace sw::universal;
51-
if (b.is_zero_block()) return dyadic();
52-
// FpType here is double in this test; <=53 bits -> exact via from_double.
53-
dyadic d = dyadic::from_double(static_cast<double>(b.v));
54-
d.scale += b.exp;
55-
return d;
56-
}
57-
58-
template <typename FpType>
59-
sw::universal::dyadic exact_value(const ZBCL<FpType>& z) {
60-
using namespace sw::universal;
61-
dyadic acc;
62-
for (const auto& blk : z.take(32)) acc = acc + exact_block(blk);
63-
return acc;
64-
}
65-
6649
// pow2 singleton ZBCL.
6750
ZBCL<double> p2(int e) {
6851
using namespace sw::universal;

elastic/elreal/arithmetic/addition.cpp

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,62 +18,13 @@
1818
#include <iostream>
1919

2020
#include <universal/number/elreal/elreal.hpp>
21-
#include <universal/verification/dyadic_exact.hpp>
21+
#include <universal/verification/elreal_oracle.hpp>
2222
#include <universal/verification/test_suite.hpp>
2323

2424
namespace {
2525

26-
// Exact value of a binary float v as a dyadic, with no precision loss at any
27-
// significand width (shared with the #1022 oracle; see its exact_real for the
28-
// full rationale). p <= 53: double is exact. p > 53 with the Universal FP API
29-
// (cfloat quad and up): read the encoding directly -- sign, scale, and the fbits
30-
// stored fraction bits -- giving (-1)^sign * (2^fbits + F) * 2^(scale - fbits).
31-
// This avoids cfloat's wide-precision frexp/floor (filed separately).
32-
template <typename T>
33-
sw::universal::dyadic exact_real(T v) {
34-
using namespace sw::universal;
35-
if (v == T(0)) return dyadic();
36-
if constexpr (std::numeric_limits<T>::digits <= 53) {
37-
return dyadic::from_double(static_cast<double>(v));
38-
} else if constexpr (has_universal_fp_api_v<T>) {
39-
constexpr int fbits = std::numeric_limits<T>::digits - 1;
40-
assert(v.isnormal() && "exact_real bit path expects a normal value");
41-
dyadic::bigint F(0);
42-
for (int i = 0; i < fbits; ++i) {
43-
if (v.test(static_cast<unsigned>(i))) {
44-
dyadic::bigint bit(1); bit <<= i; F = F + bit;
45-
}
46-
}
47-
dyadic::bigint M(1); M <<= fbits; M = M + F; // hidden bit + fraction
48-
return dyadic(v.sign() ? -M : M, v.scale() - fbits);
49-
} else {
50-
static_assert(has_universal_fp_api_v<T>,
51-
"exact_real: wide native hosts not supported yet (add std::frexp path).");
52-
return dyadic();
53-
}
54-
}
55-
56-
// Exact value of a block as a dyadic rational (value(b) = v * 2^exp), shared
57-
// with the #1022 oracle.
58-
template <typename FpType>
59-
sw::universal::dyadic exact_block(const sw::universal::block<FpType>& b) {
60-
using namespace sw::universal;
61-
if (b.is_zero_block()) return dyadic();
62-
dyadic d = exact_real(b.v);
63-
d.scale += b.exp;
64-
return d;
65-
}
66-
67-
template <typename FpType>
68-
sw::universal::dyadic exact_value(const sw::universal::ZBCL<FpType>& z) {
69-
using namespace sw::universal;
70-
dyadic acc;
71-
// 32-block window, matching the #1022 oracle's ZBCL_EXACT_WINDOW so both
72-
// files agree on the exact value of the same stream; finite test sums settle
73-
// well within it.
74-
for (const auto& blk : z.take(32)) acc = acc + exact_block(blk);
75-
return acc;
76-
}
26+
// exact_real / exact_block / exact_value: the shared exact-dyadic oracle (#1035).
27+
using namespace sw::universal::elreal_oracle;
7728

7829
// Value preservation, validated against an INDEPENDENT exact dyadic oracle
7930
// (not long double -- see #1022). double and float are round-to-nearest, so

elastic/elreal/arithmetic/arithmetic_oracle.hpp

Lines changed: 0 additions & 83 deletions
This file was deleted.

elastic/elreal/arithmetic/division.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
#include <universal/verification/dyadic_exact.hpp>
2323
#include <universal/verification/test_suite.hpp>
2424

25-
#include "arithmetic_oracle.hpp"
25+
#include <universal/verification/elreal_oracle.hpp>
2626

2727
namespace {
2828

29-
namespace est = sw::universal::elreal_arith_test;
29+
namespace est = sw::universal::elreal_oracle;
3030

3131
template <typename FpType>
3232
int verify_all(const std::string& host) {

elastic/elreal/arithmetic/exact_value_oracle.cpp

Lines changed: 10 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -67,85 +67,21 @@
6767
#include <universal/number/cfloat/cfloat.hpp>
6868
#include <universal/number/bfloat16/bfloat16.hpp>
6969
#include <universal/number/elreal/elreal.hpp>
70-
#include <universal/verification/dyadic_exact.hpp>
70+
#include <universal/verification/elreal_oracle.hpp>
7171
#include <universal/verification/test_suite.hpp>
7272

7373
namespace {
7474

7575
using namespace sw::universal;
76+
// exact_real / exact_block / exact_blocks / exact_value + ZBCL_EXACT_WINDOW: the
77+
// shared exact-dyadic oracle this file pioneered (#1022), now consolidated (#1035).
78+
using namespace sw::universal::elreal_oracle;
7679

77-
// Exact value of a binary floating-point value v as a dyadic rational, with NO
78-
// precision loss at any significand width.
79-
//
80-
// Two regimes, chosen at compile time:
81-
//
82-
// * p = digits <= 53: double represents v exactly, so from_double is exact and
83-
// cheap. Covers float(24), double(53), half(11), bfloat16(8), cfloat<24,5>(19),
84-
// cfloat<32,8>(24) -- every <= 53-bit host.
85-
//
86-
// * p > 53 with the Universal FP API (cfloat quad and up): read the encoding
87-
// DIRECTLY -- sign, scale, and the fbits stored fraction bits -- and assemble
88-
// value = (-1)^sign * (2^fbits + F) * 2^(scale - fbits), fbits = p - 1.
89-
// Reading the encoding directly keeps this oracle self-contained: it depends
90-
// on no cfloat math function, only on the bit layout. (Historically it also
91-
// side-stepped two wide-precision cfloat bugs that an earlier frexp/floor
92-
// extraction tripped over -- cfloat floor mis-handling large integers, #1026,
93-
// and cfloat frexp's non-std [1,2) fraction, #1027; both now fixed. That
94-
// earlier extraction had passed #1023's quad sweeps only because they fed
95-
// double-derived <= 53-bit q128 values, silently mis-extracting genuine
96-
// 113-bit ones.) The bit-based path is verified consistent across widths and
97-
// against an independent 2x-wider cfloat product.
98-
template <typename T>
99-
dyadic exact_real(T v) {
100-
if (v == T(0)) return dyadic();
101-
if constexpr (std::numeric_limits<T>::digits <= 53) {
102-
return dyadic::from_double(static_cast<double>(v));
103-
} else if constexpr (has_universal_fp_api_v<T>) {
104-
constexpr int fbits = std::numeric_limits<T>::digits - 1;
105-
assert(v.isnormal() && "exact_real bit path expects a normal value");
106-
dyadic::bigint F(0);
107-
for (int i = 0; i < fbits; ++i) {
108-
if (v.test(static_cast<unsigned>(i))) {
109-
dyadic::bigint bit(1); bit <<= i; F = F + bit;
110-
}
111-
}
112-
dyadic::bigint M(1); M <<= fbits; M = M + F; // hidden bit + fraction
113-
return dyadic(v.sign() ? -M : M, v.scale() - fbits);
114-
} else {
115-
static_assert(has_universal_fp_api_v<T>,
116-
"exact_real: wide (>53-bit) native hosts are not supported yet; add a "
117-
"std::frexp-based extraction (std frexp is correct for native types) "
118-
"when such a host is introduced.");
119-
return dyadic();
120-
}
121-
}
122-
123-
// exact value of a single block as a dyadic rational (value(b) = v * 2^exp).
124-
// Shares no code with the block/EFT/threeAdd/add algorithms under test.
125-
template <typename FpType>
126-
dyadic exact_block(const block<FpType>& b) {
127-
if (b.is_zero_block()) return dyadic();
128-
dyadic d = exact_real(b.v);
129-
d.scale += b.exp; // multiply by 2^exp exactly (value = v * 2^exp)
130-
return d;
131-
}
132-
133-
template <typename FpType>
134-
dyadic exact_blocks(const std::vector<block<FpType>>& bs) {
135-
dyadic acc; // 0
136-
for (const auto& b : bs) acc = acc + exact_block(b);
137-
return acc;
138-
}
139-
140-
// Number of ZBCL blocks forced when computing an exact value. Finite sums of the
141-
// test inputs settle well within this; kept identical to addition.cpp's window
142-
// so the two files agree on the "exact value" of the same stream.
143-
constexpr std::size_t ZBCL_EXACT_WINDOW = 32;
144-
145-
template <typename FpType>
146-
dyadic exact_zbcl(const ZBCL<FpType>& z) {
147-
return exact_blocks(z.take(ZBCL_EXACT_WINDOW));
148-
}
80+
// The exact-dyadic helpers (exact_real / exact_block / exact_blocks / exact_value)
81+
// and ZBCL_EXACT_WINDOW that this oracle pioneered (#1022) now live in the shared
82+
// header <universal/verification/elreal_oracle.hpp> (#1035) and are brought into
83+
// scope by the using-directive above. The EFT/threeAdd/add reference checks below
84+
// build on them but share no code with the algorithms under test.
14985

15086
// value of a block in double, and its ulp -- used only for the bfloat16
15187
// truncation bound (double has ~45 bits of headroom over bfloat16's 8).
@@ -225,7 +161,7 @@ int check_add_exact(double a, double b, const std::string& tag) {
225161
auto za = from_native<FpType>(a);
226162
auto zb = from_native<FpType>(b);
227163
auto z = add(za, zb);
228-
if ((exact_zbcl(za) + exact_zbcl(zb)) != exact_zbcl(z)) {
164+
if ((exact_value(za) + exact_value(zb)) != exact_value(z)) {
229165
std::cout << tag << " add value WRONG: a=" << a << " b=" << b << "\n";
230166
return 1;
231167
}

elastic/elreal/arithmetic/multiplication.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
#include <universal/verification/dyadic_exact.hpp>
1919
#include <universal/verification/test_suite.hpp>
2020

21-
#include "arithmetic_oracle.hpp"
21+
#include <universal/verification/elreal_oracle.hpp>
2222

2323
namespace {
2424

25-
namespace est = sw::universal::elreal_arith_test;
25+
namespace est = sw::universal::elreal_oracle;
2626

2727
// exact_value(mul(x,y)) == exact(x) * exact(y), plus 0-overlap.
2828
template <typename FpType>

elastic/elreal/arithmetic/negation.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
#include <universal/verification/dyadic_exact.hpp>
1919
#include <universal/verification/test_suite.hpp>
2020

21-
#include "arithmetic_oracle.hpp"
21+
#include <universal/verification/elreal_oracle.hpp>
2222

2323
namespace {
2424

25-
namespace est = sw::universal::elreal_arith_test;
25+
namespace est = sw::universal::elreal_oracle;
2626

2727
template <typename FpType>
2828
int verify_one(double v, const std::string& tag) {

elastic/elreal/math/constants.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@
3636
#include <universal/verification/dyadic_exact.hpp>
3737
#include <universal/verification/test_suite.hpp>
3838

39-
#include "../arithmetic/arithmetic_oracle.hpp"
39+
#include <universal/verification/elreal_oracle.hpp>
4040

4141
namespace {
4242

43-
namespace est = sw::universal::elreal_arith_test;
43+
namespace est = sw::universal::elreal_oracle;
4444

4545
template <typename FpType>
4646
int check_value(const sw::universal::ZBCL<FpType>& z, double ref, double tol, const std::string& tag) {

elastic/elreal/math/exponent.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
#include <universal/verification/dyadic_exact.hpp>
2727
#include <universal/verification/test_suite.hpp>
2828

29-
#include "../arithmetic/arithmetic_oracle.hpp"
29+
#include <universal/verification/elreal_oracle.hpp>
3030

3131
namespace {
3232

33-
namespace est = sw::universal::elreal_arith_test;
33+
namespace est = sw::universal::elreal_oracle;
3434

3535
template <typename FpType>
3636
int near(const sw::universal::ZBCL<FpType>& z, double ref, double tol, const std::string& tag) {

elastic/elreal/math/hyperbolic.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
#include <universal/verification/dyadic_exact.hpp>
2121
#include <universal/verification/test_suite.hpp>
2222

23-
#include "../arithmetic/arithmetic_oracle.hpp"
23+
#include <universal/verification/elreal_oracle.hpp>
2424

2525
namespace {
2626

27-
namespace est = sw::universal::elreal_arith_test;
27+
namespace est = sw::universal::elreal_oracle;
2828

2929
template <typename FpType>
3030
int near(const sw::universal::ZBCL<FpType>& z, double ref, double tol, const std::string& tag) {

0 commit comments

Comments
 (0)