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

stillwater-sc / universal / 24948122842

26 Apr 2026 04:21AM UTC coverage: 84.291% (-0.05%) from 84.337%
24948122842

Pull #770

github

web-flow
Merge 263f468b1 into 11fbc5822
Pull Request #770: feat(math): add constexpr pow to sw::math::constexpr_math

109 of 155 new or added lines in 3 files covered. (70.32%)

4 existing lines in 1 file now uncovered.

45345 of 53796 relevant lines covered (84.29%)

6375897.53 hits per line

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

61.11
/internal/constexpr_math/api/pow.cpp
1
// pow.cpp: regression for sw::math::constexpr_math::pow (integer + general overloads)
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

8
#include <cmath>
9
#include <iostream>
10
#include <iomanip>
11
#include <limits>
12

13
#include <math/constexpr_math.hpp>
14

15
namespace cm = sw::math::constexpr_math;
16

17
// ============================================================================
18
// Compile-time correctness via static_assert
19
// ============================================================================
20

21
// ----------------------------------------------------------------------------
22
// Integer-exponent fast path (pow(T, int))
23
// ----------------------------------------------------------------------------
24

25
// Positive integer exponent
26
static_assert(cm::pow(2.0, 0)   == 1.0,    "pow(2, 0) == 1");
27
static_assert(cm::pow(2.0, 1)   == 2.0,    "pow(2, 1) == 2");
28
static_assert(cm::pow(2.0, 10)  == 1024.0, "pow(2, 10) == 1024");
29
static_assert(cm::pow(3.0, 4)   == 81.0,   "pow(3, 4) == 81");
30
static_assert(cm::pow(0.5, 8)   == 1.0/256.0, "pow(0.5, 8) == 1/256");
31

32
// Negative integer exponent
33
static_assert(cm::pow(2.0, -1)  == 0.5,    "pow(2, -1) == 0.5");
34
static_assert(cm::pow(2.0, -3)  == 0.125,  "pow(2, -3) == 0.125");
35
static_assert(cm::pow(2.0, -10) == 1.0/1024.0, "pow(2, -10) == 1/1024");
36

37
// Negative base, integer exponent: sign depends on parity
38
static_assert(cm::pow(-2.0, 3)  == -8.0,   "pow(-2, 3) == -8 (odd exponent)");
39
static_assert(cm::pow(-2.0, 4)  == 16.0,   "pow(-2, 4) == 16 (even exponent)");
40
static_assert(cm::pow(-1.0, 100) == 1.0,   "pow(-1, 100) == 1");
41
static_assert(cm::pow(-1.0, 101) == -1.0,  "pow(-1, 101) == -1");
42

43
// Float overload
44
static_assert(cm::pow(2.0f, 10) == 1024.0f, "powf(2, 10) == 1024");
45
static_assert(cm::pow(0.5f, 8)  == 1.0f/256.0f, "powf(0.5, 8) == 1/256");
46
static_assert(cm::pow(-2.0f, 5) == -32.0f, "powf(-2, 5) == -32");
47

48
// ----------------------------------------------------------------------------
49
// General overload pow(T, T) -- integer arguments via fast-path
50
// ----------------------------------------------------------------------------
51

52
// When the floating-point exponent is exactly an integer, the integer fast
53
// path is dispatched; results match the integer overload bit-for-bit.
54
static_assert(cm::pow(2.0, 10.0)  == 1024.0, "pow(2.0, 10.0) == 1024 via fast path");
55
static_assert(cm::pow(3.0, 4.0)   == 81.0,   "pow(3.0, 4.0) == 81 via fast path");
56
static_assert(cm::pow(-2.0, 3.0)  == -8.0,   "pow(-2.0, 3.0) == -8 via fast path");
57
static_assert(cm::pow(-1.0, 100.0) == 1.0,   "pow(-1.0, 100.0) == 1 via fast path");
58

59
// Negative-base + integer exponent must use the squaring fast path for exact
60
// integer-power semantics (regression for the CodeRabbit Major fix).
61
static_assert(cm::pow(-3.0, 5.0)  == -243.0, "pow(-3.0, 5.0) == -243 via fast path (negative base, odd exp)");
62
static_assert(cm::pow(-3.0, 4.0)  == 81.0,   "pow(-3.0, 4.0) == 81 via fast path (negative base, even exp)");
63
static_assert(cm::pow(-7.0, 3.0)  == -343.0, "pow(-7.0, 3.0) == -343 via fast path");
64
static_assert(cm::pow(-2.0, -3.0) == -0.125, "pow(-2.0, -3.0) == -0.125 via fast path (negative base, negative odd exp)");
65
static_assert(cm::pow(-2.0, -4.0) == 0.0625, "pow(-2.0, -4.0) == 0.0625 via fast path (negative base, negative even exp)");
66

67
// Float overload regression for the same fix.
68
static_assert(cm::pow(-3.0f, 5.0f) == -243.0f, "powf(-3.0, 5.0) == -243 via fast path");
69
static_assert(cm::pow(-3.0f, 4.0f) == 81.0f,   "powf(-3.0, 4.0) == 81 via fast path");
70

71
// ----------------------------------------------------------------------------
72
// General overload pow(T, T) -- transcendental path
73
// ----------------------------------------------------------------------------
74

75
// pow(2, 0.5) == sqrt(2) ~= 1.4142135623730951
76
constexpr double cx_sqrt2 = cm::pow(2.0, 0.5);
77
static_assert(cx_sqrt2 > 1.4142135 && cx_sqrt2 < 1.4142137,
78
              "pow(2.0, 0.5) ~= sqrt(2)");
79

80
// pow(8, 1/3) ~= 2.0 (within transcendental round-trip error)
81
constexpr double cx_cbrt8 = cm::pow(8.0, 1.0/3.0);
82
static_assert(cx_cbrt8 > 1.99999 && cx_cbrt8 < 2.00001,
83
              "pow(8, 1/3) ~= 2");
84

85
// ----------------------------------------------------------------------------
86
// IEEE-754 special-case table (C99 7.12.7.4)
87
// ----------------------------------------------------------------------------
88

89
// pow(x, 0) == 1 for any x including NaN, infinity
90
static_assert(cm::pow(0.0, 0.0)   == 1.0, "pow(0, 0) == 1");
91
static_assert(cm::pow(-0.0, 0.0)  == 1.0, "pow(-0, 0) == 1");
92
static_assert(cm::pow(2.0, 0.0)   == 1.0, "pow(2, 0) == 1");
93
static_assert(cm::pow(-3.5, 0.0)  == 1.0, "pow(-3.5, 0) == 1");
94
static_assert(cm::pow(std::numeric_limits<double>::infinity(), 0.0) == 1.0,
95
              "pow(+inf, 0) == 1");
96
static_assert(cm::pow(std::numeric_limits<double>::quiet_NaN(), 0.0) == 1.0,
97
              "pow(NaN, 0) == 1");
98

99
// pow(1, y) == 1 for any y including NaN, infinity
100
static_assert(cm::pow(1.0, 5.0) == 1.0, "pow(1, 5) == 1");
101
static_assert(cm::pow(1.0, std::numeric_limits<double>::infinity()) == 1.0,
102
              "pow(1, +inf) == 1");
103
static_assert(cm::pow(1.0, std::numeric_limits<double>::quiet_NaN()) == 1.0,
104
              "pow(1, NaN) == 1");
105

106
// NaN propagation when neither override applies
107
static_assert(cm::pow(std::numeric_limits<double>::quiet_NaN(), 2.0)
108
              != cm::pow(std::numeric_limits<double>::quiet_NaN(), 2.0),
109
              "pow(NaN, 2) == NaN");
110
static_assert(cm::pow(2.0, std::numeric_limits<double>::quiet_NaN())
111
              != cm::pow(2.0, std::numeric_limits<double>::quiet_NaN()),
112
              "pow(2, NaN) == NaN");
113

114
// pow(-1, +/-inf) == 1
115
static_assert(cm::pow(-1.0,  std::numeric_limits<double>::infinity()) == 1.0,
116
              "pow(-1, +inf) == 1");
117
static_assert(cm::pow(-1.0, -std::numeric_limits<double>::infinity()) == 1.0,
118
              "pow(-1, -inf) == 1");
119

120
// |x| < 1, +/-inf
121
static_assert(cm::pow(0.5,  std::numeric_limits<double>::infinity()) == 0.0,
122
              "pow(0.5, +inf) == 0");
123
static_assert(cm::pow(0.5, -std::numeric_limits<double>::infinity())
124
              == std::numeric_limits<double>::infinity(),
125
              "pow(0.5, -inf) == +inf");
126

127
// |x| > 1, +/-inf
128
static_assert(cm::pow(2.0,  std::numeric_limits<double>::infinity())
129
              == std::numeric_limits<double>::infinity(),
130
              "pow(2, +inf) == +inf");
131
static_assert(cm::pow(2.0, -std::numeric_limits<double>::infinity()) == 0.0,
132
              "pow(2, -inf) == 0");
133

134
// pow(+inf, y < 0) == 0;  pow(+inf, y > 0) == +inf
135
static_assert(cm::pow(std::numeric_limits<double>::infinity(), -1.0) == 0.0,
136
              "pow(+inf, -1) == 0");
137
static_assert(cm::pow(std::numeric_limits<double>::infinity(), 2.0)
138
              == std::numeric_limits<double>::infinity(),
139
              "pow(+inf, 2) == +inf");
140

141
// pow(-inf, ...): sign depends on exponent parity
142
static_assert(cm::pow(-std::numeric_limits<double>::infinity(), 3.0)
143
              == -std::numeric_limits<double>::infinity(),
144
              "pow(-inf, 3) == -inf (odd integer)");
145
static_assert(cm::pow(-std::numeric_limits<double>::infinity(), 4.0)
146
              == std::numeric_limits<double>::infinity(),
147
              "pow(-inf, 4) == +inf (even integer)");
148
static_assert(cm::pow(-std::numeric_limits<double>::infinity(), -3.0) == -0.0,
149
              "pow(-inf, -3) == -0 (odd integer, neg)");
150
static_assert(cm::pow(-std::numeric_limits<double>::infinity(), -4.0) == 0.0,
151
              "pow(-inf, -4) == +0 (even integer, neg)");
152

153
// pow(+/-0, y > 0) -- y odd integer preserves sign of zero
154
static_assert(cm::pow(0.0, 3.0)  == 0.0, "pow(+0, 3) == +0");
155
static_assert(cm::pow(-0.0, 3.0) == -0.0, "pow(-0, 3) == -0 (odd integer)");
156
static_assert(cm::pow(-0.0, 4.0) == 0.0,  "pow(-0, 4) == +0 (even integer)");
157
static_assert(cm::pow(-0.0, 2.5) == 0.0,  "pow(-0, 2.5) == +0 (non-integer)");
158

159
// pow(+/-0, y < 0) -- y odd integer preserves sign of infinity
160
static_assert(cm::pow(0.0, -1.0)
161
              == std::numeric_limits<double>::infinity(),
162
              "pow(+0, -1) == +inf");
163
static_assert(cm::pow(-0.0, -3.0)
164
              == -std::numeric_limits<double>::infinity(),
165
              "pow(-0, -3) == -inf (odd integer)");
166
static_assert(cm::pow(-0.0, -4.0)
167
              == std::numeric_limits<double>::infinity(),
168
              "pow(-0, -4) == +inf (even integer)");
169
static_assert(cm::pow(-0.0, -2.5)
170
              == std::numeric_limits<double>::infinity(),
171
              "pow(-0, -2.5) == +inf (non-integer)");
172

173
// pow(x < 0, finite non-integer y) == NaN
174
static_assert(cm::pow(-2.0, 0.5)
175
              != cm::pow(-2.0, 0.5),
176
              "pow(-2, 0.5) == NaN");
177
static_assert(cm::pow(-3.5, 1.5)
178
              != cm::pow(-3.5, 1.5),
179
              "pow(-3.5, 1.5) == NaN");
180

181
// ============================================================================
182
// Runtime cross-check vs std::pow
183
// ============================================================================
184

185
int main() {
1✔
186
        std::cout << "sw::math::constexpr_math::pow verification\n";
1✔
187

188
        int errors = 0;
1✔
189
        auto check = [&](const char* name, double x, double y, double our, double ref, double tol) {
21✔
190
                if (ref != ref) {
21✔
191
                        // Reference is NaN; expect our to be NaN too.
NEW
192
                        if (our == our) {
×
NEW
193
                                ++errors;
×
NEW
194
                                std::cout << "FAIL " << name << "  x=" << x << "  y=" << y
×
NEW
195
                                          << "  our=" << our << "  ref=NaN (expected NaN)\n";
×
196
                        }
NEW
197
                        return;
×
198
                }
199
                double err = (ref == 0.0) ? std::abs(our) : std::abs((our - ref) / ref);
21✔
200
                if (err > tol) {
21✔
NEW
201
                        ++errors;
×
NEW
202
                        std::cout << "FAIL " << name << "  x=" << x << "  y=" << y
×
NEW
203
                                  << "  our=" << std::setprecision(17) << our
×
NEW
204
                                  << "  ref=" << ref
×
NEW
205
                                  << "  rel-err=" << err << '\n';
×
206
                }
207
        };
1✔
208

209
        // Sweep of (base, exponent) pairs against std::pow.
210
        struct Pair { double x; double y; };
211
        const Pair pts[] = {
1✔
212
                // Integer exponents (fast path)
213
                { 2.0,   10.0  }, { 2.0,  -10.0 }, { 0.5,  20.0 }, { 1.5,  7.0  },
214
                { 3.0,    4.0  }, { 10.0,  3.0  }, { 7.0,  -2.0 }, { 1.1,  100.0},
215
                // Negative base, integer exponent
216
                { -2.0,   3.0  }, { -2.0,   4.0 }, { -1.0, 7.0  }, { -1.5, 5.0  },
217
                // Non-integer exponent (transcendental path)
218
                { 2.0,    0.5  }, { 8.0, 1.0/3.0}, { 10.0, 0.301029995663981}, // ~ log10(2)
219
                { 1.5,    2.7  }, { 0.7,  -1.5  }, { 100.0,  0.5 },
220
                // Large dynamic range
221
                { 1e10,   2.0  }, { 1e-10, 3.0  }, { 1.001, 1000.0 },
222
        };
223
        for (auto p : pts) {
22✔
224
                double our = cm::pow(p.x, p.y);
21✔
225
                double ref = std::pow(p.x, p.y);
21✔
226
                check("double sweep", p.x, p.y, our, ref, 1e-13);
21✔
227
        }
228

229
        // Float sweep
230
        struct PairF { float x; float y; };
231
        const PairF fpts[] = {
1✔
232
                { 2.0f, 10.0f }, { 2.0f, -10.0f }, { 0.5f, 8.0f }, { 3.14f, 2.5f },
233
                { -2.0f, 3.0f }, { -1.0f, 100.0f }, { 1.5f, 0.5f },
234
        };
235
        for (auto p : fpts) {
8✔
236
                float our = cm::pow(p.x, p.y);
7✔
237
                float ref = std::pow(p.x, p.y);
7✔
238
                double err = (ref == 0.0f) ? std::abs(our) : std::abs((our - ref) / ref);
7✔
239
                if (err > 1e-5) {  // allow a bit more for float
7✔
NEW
240
                        ++errors;
×
NEW
241
                        std::cout << "FAIL float sweep  x=" << p.x << "  y=" << p.y
×
NEW
242
                                  << "  our=" << our << "  ref=" << ref
×
NEW
243
                                  << "  rel-err=" << err << '\n';
×
244
                }
245
        }
246

247
        std::cout << "constexpr_math::pow: " << (errors == 0 ? "PASS" : "FAIL")
1✔
248
                  << " (" << errors << " error" << (errors == 1 ? "" : "s") << ")\n";
1✔
249
        return (errors == 0) ? 0 : 1;
1✔
250
}
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