• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stillwater-sc / universal / 26478780477

26 May 2026 10:28PM UTC coverage: 84.011% (-0.08%) from 84.087%
26478780477

push

github

web-flow
fix(ereal): deliver extended precision in mathlib transcendentals (#1002) (#1004)

* fix(ereal): deliver extended precision in mathlib transcendentals (#1002)

Root cause: every transcendental that used a math constant initialized it from
a double literal -- e.g. `Real pi(3.14159...100 digits...)` or `Real ln2(...)`.
A double literal carries only ~16 digits, so the long string was truncated
before the ereal saw it, capping sin/cos/exp2/exp10/log2/log10/... at double
precision regardless of maxlimbs.

Fix:
- ereal_constants.hpp: real high-precision constants. pi, ln2, ln10 are parsed
  from verified decimal strings (parse() builds a true multi-component
  expansion); everything else is derived via exact operations (scale by powers
  of two / small ints) or extended-precision algorithms (ereal sqrt/exp/divide
  are themselves extended-precision), so correctness follows from the algorithm
  rather than hand-transcribed tail digits. Each accessor caches its value.
- mathlib.hpp: include the constants header (it was dead code -- nothing
  included it, which is why the functions inlined their own truncated copies).
- exponent.hpp / logarithm.hpp / trigonometry.hpp: use the shared
  ereal_pi/pi_2/2pi/ln2/ln10 constants instead of inline double-literal copies.

Result (verified): sin/cos/exp/log now scale with maxlimbs --
sin^2 + cos^2 - 1 == 1.8e-133, exp(ln2) - 2 == 7e-120, exp(1) == e exactly,
two independent computations of cos(0.3) agree to ~101 digits.

progressive_precision.cpp: was failing 56 (then 24) precision-scaling checks --
but several of its hardcoded MPFR reference strings were generated incorrectly
(cos(0.3)'s was wrong beyond ~16 digits; the function is correct). Redesigned to
be self-referential: each ereal<N> is measured against the function evaluated at
a higher maxlimbs, with the reference level scaled by REGRESSION_LEVEL (L1
references ereal<8> for fast CI; L4 references ereal<19>). Added the missing
MANUAL_TESTING/REGRESSION_LEVEL guar... (continued)

41 of 43 new or added lines in 6 files covered. (95.35%)

232 existing lines in 14 files now uncovered.

46820 of 55731 relevant lines covered (84.01%)

6420694.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.0
/internal/expansion/api/compression.cpp
1
// compression.cpp: Tests for expansion compression operations
2
//
3
// Copyright (C) 2017 Stillwater Supercomputing, Inc.
4
// SPDX-License-Identifier: MIT
5
//
6
// This file is part of the universal numbers project, which is released under an MIT Open Source license.
7
#include <universal/utility/directives.hpp>
8
#include <universal/utility/long_double.hpp>
9
#include <universal/utility/bit_cast.hpp>
10
#include <universal/number/cfloat/cfloat.hpp>
11
#include <universal/verification/test_suite.hpp>
12
#include <universal/internal/expansion/expansion_ops.hpp>
13

14
namespace sw { namespace universal {
15

16
        // Test SCALE-EXPANSION
17
        int test_scale_expansion() {
1✔
18
                using namespace expansion_ops;
19
                int nrOfFailedTests = 0;
1✔
20

21
                std::cout << "Testing SCALE-EXPANSION\n";
1✔
22

23
                // Test case 1: Scale by 2.0
24
                {
25
                        std::vector<double> e = { 3.0, 5.0e-16 };
2✔
26
                        double b = 2.0;
1✔
27

28
                        std::vector<double> h = scale_expansion(e, b);
1✔
29

30
                        // Verify value: h should equal e * b
31
                        double e_sum = 0.0, h_sum = 0.0;
1✔
32
                        for (auto v : e) e_sum += v;
3✔
33
                        for (auto v : h) h_sum += v;
3✔
34

35
                        double expected = e_sum * b;
1✔
36
                        if (std::abs(h_sum - expected) > 1.0e-14) ++nrOfFailedTests;
1✔
37
                }
1✔
38

39
                // Test case 2: Scale by 0.0
40
                {
41
                        std::vector<double> e = { 10.0, 1.0e-15 };
2✔
42
                        double b = 0.0;
1✔
43

44
                        std::vector<double> h = scale_expansion(e, b);
1✔
45

46
                        if (h.size() != 1) ++nrOfFailedTests;
1✔
47
                        if (h[0] != 0.0) ++nrOfFailedTests;
1✔
48
                }
1✔
49

50
                // Test case 3: Scale by 1.0 (should return unchanged)
51
                {
52
                        std::vector<double> e = { 7.0, 3.5e-16 };
2✔
53
                        double b = 1.0;
1✔
54

55
                        std::vector<double> h = scale_expansion(e, b);
1✔
56

57
                        if (h.size() != e.size()) ++nrOfFailedTests;
1✔
58
                        for (size_t i = 0; i < e.size(); ++i) {
3✔
59
                                if (h[i] != e[i]) ++nrOfFailedTests;
2✔
60
                        }
61
                }
1✔
62

63
                // Test case 4: Scale by -1.0 (negation)
64
                {
65
                        std::vector<double> e = { 5.0, 2.5e-16 };
2✔
66
                        double b = -1.0;
1✔
67

68
                        std::vector<double> h = scale_expansion(e, b);
1✔
69

70
                        if (h.size() != e.size()) ++nrOfFailedTests;
1✔
71
                        for (size_t i = 0; i < e.size(); ++i) {
3✔
72
                                if (h[i] != -e[i]) ++nrOfFailedTests;
2✔
73
                        }
74
                }
1✔
75

76
                // Test case 5: Scale by fractional value
77
                {
78
                        std::vector<double> e = { 10.0, 1.0e-15 };
2✔
79
                        double b = 0.3;
1✔
80

81
                        std::vector<double> h = scale_expansion(e, b);
1✔
82

83
                        double e_sum = 0.0, h_sum = 0.0;
1✔
84
                        for (auto v : e) e_sum += v;
3✔
85
                        for (auto v : h) h_sum += v;
4✔
86

87
                        double expected = e_sum * b;
1✔
88
                        if (std::abs(h_sum - expected) > 1.0e-13) ++nrOfFailedTests;
1✔
89
                }
1✔
90

91
                return nrOfFailedTests;
1✔
92
        }
93

94
        // Test COMPRESS-EXPANSION
95
        int test_compress_expansion() {
1✔
96
                using namespace expansion_ops;
97
                int nrOfFailedTests = 0;
1✔
98

99
                std::cout << "Testing COMPRESS-EXPANSION\n";
1✔
100

101
                // Test case 1: Remove exact zeros
102
                {
103
                        std::vector<double> e = { 10.0, 0.0, 1.0e-15, 0.0, 5.0e-30 };
2✔
104

105
                        std::vector<double> h = compress_expansion(e, 0.0);
1✔
106

107
                        // Should remove the two 0.0 components
108
                        if (h.size() >= e.size()) ++nrOfFailedTests;
1✔
109

110
                        // Verify no zeros remain
111
                        for (auto v : h) {
4✔
112
                                if (v == 0.0) ++nrOfFailedTests;
3✔
113
                        }
114
                }
1✔
115

116
                // Test case 2: Aggressive compression (relative threshold)
117
                {
118
                        std::vector<double> e = { 1.0, 1.0e-10, 1.0e-20, 1.0e-30 };
2✔
119

120
                        // Remove components < 1e-15 * largest
121
                        std::vector<double> h = compress_expansion(e, 1.0e-15);
1✔
122

123
                        // Should keep 1.0 and 1.0e-10, discard 1.0e-20 and 1.0e-30
124
                        if (h.size() > 2) ++nrOfFailedTests;
1✔
125

126
                        // Verify largest components are preserved
127
                        if (h[0] != 1.0) ++nrOfFailedTests;
1✔
128
                }
1✔
129

130
                // Test case 3: All zeros
131
                {
132
                        std::vector<double> e = { 0.0, 0.0, 0.0 };
2✔
133

134
                        std::vector<double> h = compress_expansion(e, 0.0);
1✔
135

136
                        // Should return single zero
137
                        if (h.size() != 1) ++nrOfFailedTests;
1✔
138
                        if (h[0] != 0.0) ++nrOfFailedTests;
1✔
139
                }
1✔
140

141
                // Test case 4: No compression needed
142
                {
143
                        std::vector<double> e = { 5.0, 2.5, 1.25 };
2✔
144

145
                        std::vector<double> h = compress_expansion(e, 0.0);
1✔
146

147
                        // Should return unchanged (no zeros)
148
                        if (h.size() != e.size()) ++nrOfFailedTests;
1✔
149
                }
1✔
150

151
                return nrOfFailedTests;
1✔
152
        }
153

154
        // Test COMPRESS-TO-N
155
        int test_compress_to_n() {
1✔
156
                using namespace expansion_ops;
157
                int nrOfFailedTests = 0;
1✔
158

159
                std::cout << "Testing COMPRESS-TO-N\n";
1✔
160

161
                // Test case 1: Reduce to 2 components
162
                {
163
                        std::vector<double> e = { 10.0, 1.0, 0.1, 0.01, 0.001 };
2✔
164

165
                        std::vector<double> h = compress_to_n(e, 2);
1✔
166

167
                        if (h.size() != 2) ++nrOfFailedTests;
1✔
168
                        if (h[0] != 10.0) ++nrOfFailedTests;
1✔
169
                        if (h[1] != 1.0) ++nrOfFailedTests;
1✔
170
                }
1✔
171

172
                // Test case 2: Request more than available
173
                {
174
                        std::vector<double> e = { 5.0, 2.5 };
2✔
175

176
                        std::vector<double> h = compress_to_n(e, 10);
1✔
177

178
                        // Should return unchanged
179
                        if (h.size() != e.size()) ++nrOfFailedTests;
1✔
180
                }
1✔
181

182
                // Test case 3: Compress to 1 (keep only most significant)
183
                {
184
                        std::vector<double> e = { 100.0, 1.0e-10, 1.0e-20 };
2✔
185

186
                        std::vector<double> h = compress_to_n(e, 1);
1✔
187

188
                        if (h.size() != 1) ++nrOfFailedTests;
1✔
189
                        if (h[0] != 100.0) ++nrOfFailedTests;
1✔
190
                }
1✔
191

192
                return nrOfFailedTests;
1✔
193
        }
194

195
        // Test SIGN-ADAPTIVE
196
        int test_sign_adaptive() {
1✔
197
                using namespace expansion_ops;
198
                int nrOfFailedTests = 0;
1✔
199

200
                std::cout << "Testing SIGN-ADAPTIVE\n";
1✔
201

202
                // Test case 1: Positive expansion
203
                {
204
                        std::vector<double> e = { 10.0, 1.0e-15 };
2✔
205
                        int sign = sign_adaptive(e);
1✔
206
                        if (sign != 1) ++nrOfFailedTests;
1✔
207
                }
1✔
208

209
                // Test case 2: Negative expansion
210
                {
211
                        std::vector<double> e = { -5.0, -2.5e-16 };
2✔
212
                        int sign = sign_adaptive(e);
1✔
213
                        if (sign != -1) ++nrOfFailedTests;
1✔
214
                }
1✔
215

216
                // Test case 3: Zero expansion
217
                {
218
                        std::vector<double> e = { 0.0, 0.0, 0.0 };
2✔
219
                        int sign = sign_adaptive(e);
1✔
220
                        if (sign != 0) ++nrOfFailedTests;
1✔
221
                }
1✔
222

223
                // Test case 4: Leading zeros (adaptive!)
224
                {
225
                        std::vector<double> e = { 0.0, 0.0, 1.0e-100 };
2✔
226
                        int sign = sign_adaptive(e);
1✔
227
                        // Should find the tiny positive component
228
                        if (sign != 1) ++nrOfFailedTests;
1✔
229
                }
1✔
230

231
                // Test case 5: Mixed signs (most significant wins)
232
                {
233
                        std::vector<double> e = { 10.0, -1.0e-15 };
2✔
234
                        int sign = sign_adaptive(e);
1✔
235
                        // First component is positive, so result is positive
236
                        if (sign != 1) ++nrOfFailedTests;
1✔
237
                }
1✔
238

239
                return nrOfFailedTests;
1✔
240
        }
241

242
        // Test COMPARE-ADAPTIVE
243
        int test_compare_adaptive() {
1✔
244
                using namespace expansion_ops;
245
                int nrOfFailedTests = 0;
1✔
246

247
                std::cout << "Testing COMPARE-ADAPTIVE\n";
1✔
248

249
                // Test case 1: e > f
250
                {
251
                        std::vector<double> e = { 10.0, 1.0e-15 };
2✔
252
                        std::vector<double> f = { 5.0, 2.5e-15 };
2✔
253

254
                        int cmp = compare_adaptive(e, f);
1✔
255
                        if (cmp != 1) ++nrOfFailedTests;
1✔
256
                }
1✔
257

258
                // Test case 2: e < f
259
                {
260
                        std::vector<double> e = { 3.0, 1.5e-16 };
2✔
261
                        std::vector<double> f = { 10.0, 5.0e-16 };
2✔
262

263
                        int cmp = compare_adaptive(e, f);
1✔
264
                        if (cmp != -1) ++nrOfFailedTests;
1✔
265
                }
1✔
266

267
                // Test case 3: e == f
268
                {
269
                        std::vector<double> e = { 7.0, 3.5e-16 };
2✔
270
                        std::vector<double> f = { 7.0, 3.5e-16 };
2✔
271

272
                        int cmp = compare_adaptive(e, f);
1✔
273
                        if (cmp != 0) ++nrOfFailedTests;
1✔
274
                }
1✔
275

276
                // Test case 4: Different sizes, same value
277
                {
278
                        std::vector<double> e = { 5.0 };
2✔
279
                        std::vector<double> f = { 5.0, 0.0, 0.0 };
2✔
280

281
                        int cmp = compare_adaptive(e, f);
1✔
282
                        if (cmp != 0) ++nrOfFailedTests;
1✔
283
                }
1✔
284

285
                // Test case 5: Early termination test
286
                {
287
                        // First component differs - should terminate immediately
288
                        std::vector<double> e = { 100.0, 50.0, 25.0, 12.5 };
2✔
289
                        std::vector<double> f = { 99.0, 50.0, 25.0, 12.5 };
2✔
290

291
                        int cmp = compare_adaptive(e, f);
1✔
292
                        // e > f because first component is larger
293
                        if (cmp != 1) ++nrOfFailedTests;
1✔
294
                }
1✔
295

296
                return nrOfFailedTests;
1✔
297
        }
298

299
}} // namespace sw::universal
300

301
#define MANUAL_TESTING 0
302
#ifndef REGRESSION_LEVEL_OVERRIDE
303
#        undef REGRESSION_LEVEL_1
304
#        undef REGRESSION_LEVEL_2
305
#        undef REGRESSION_LEVEL_3
306
#        undef REGRESSION_LEVEL_4
307
#        define REGRESSION_LEVEL_1 1
308
#        define REGRESSION_LEVEL_2 1
309
#        define REGRESSION_LEVEL_3 0
310
#        define REGRESSION_LEVEL_4 0
311
#endif
312

313
int main()
1✔
314
try {
315
        using namespace sw::universal;
316

317
        std::string test_suite  = "expansion compression and adaptive operations";
2✔
318
        std::string test_tag    = "compression";
1✔
319
        bool reportTestCases    = true;
1✔
320
        int nrOfFailedTestCases = 0;
1✔
321

322
        ReportTestSuiteHeader(test_suite, reportTestCases);
1✔
323

324
#if REGRESSION_LEVEL_1
325

326
        nrOfFailedTestCases += ReportTestResult(test_scale_expansion(),    "expansion", "scale_expansion");
4✔
327
        nrOfFailedTestCases += ReportTestResult(test_compress_expansion(), "expansion", "compress_expansion");
4✔
328
        nrOfFailedTestCases += ReportTestResult(test_compress_to_n(),      "expansion", "compress_to_n");
4✔
329
        nrOfFailedTestCases += ReportTestResult(test_sign_adaptive(),      "expansion", "sign adaptive");
4✔
330
        nrOfFailedTestCases += ReportTestResult(test_compare_adaptive(),   "expansion", "compare adaptive");
3✔
331

332
#endif
333
        ReportTestSuiteResults(test_suite, nrOfFailedTestCases);
1✔
334
        return (nrOfFailedTestCases > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
1✔
335
}
1✔
UNCOV
336
catch (const std::exception& e) {
×
UNCOV
337
        std::cerr << "Caught exception: " << e.what() << std::endl;
×
UNCOV
338
        return EXIT_FAILURE;
×
UNCOV
339
}
×
UNCOV
340
catch (...) {
×
UNCOV
341
        std::cerr << "Caught unknown exception" << std::endl;
×
UNCOV
342
        return EXIT_FAILURE;
×
UNCOV
343
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc