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

stillwater-sc / universal / 24947001277

26 Apr 2026 02:53AM UTC coverage: 84.332% (+0.004%) from 84.328%
24947001277

Pull #769

github

web-flow
Merge 3b69feb9d into 18fbaa6e7
Pull Request #769: feat(math): add constexpr exp2 to sw::math::constexpr_math

105 of 114 new or added lines in 3 files covered. (92.11%)

10 existing lines in 2 files now uncovered.

45201 of 53599 relevant lines covered (84.33%)

6397323.54 hits per line

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

73.53
/internal/constexpr_math/api/exp2.cpp
1
// exp2.cpp: regression for sw::math::constexpr_math::exp2(float|double)
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
// Exact integer powers of 2: must be bit-exact (the 2^N path).
22
static_assert(cm::exp2(0.0)    == 1.0,    "exp2(0) == 1");
23
static_assert(cm::exp2(1.0)    == 2.0,    "exp2(1) == 2");
24
static_assert(cm::exp2(2.0)    == 4.0,    "exp2(2) == 4");
25
static_assert(cm::exp2(10.0)   == 1024.0, "exp2(10) == 1024");
26
static_assert(cm::exp2(-1.0)   == 0.5,    "exp2(-1) == 0.5");
27
static_assert(cm::exp2(-10.0)  == 1.0/1024.0, "exp2(-10) == 1/1024");
28

29
// Float overload, exact integer powers
30
static_assert(cm::exp2(0.0f)   == 1.0f,    "exp2f(0) == 1");
31
static_assert(cm::exp2(8.0f)   == 256.0f,  "exp2f(8) == 256");
32
static_assert(cm::exp2(-3.0f)  == 0.125f,  "exp2f(-3) == 0.125");
33

34
// Float special values mirroring the double safeguards.
35
static_assert(cm::exp2(std::numeric_limits<float>::infinity())
36
              == std::numeric_limits<float>::infinity(),
37
              "exp2f(+inf) == +inf");
38
static_assert(cm::exp2(-std::numeric_limits<float>::infinity()) == 0.0f,
39
              "exp2f(-inf) == 0");
40
static_assert(cm::exp2(std::numeric_limits<float>::quiet_NaN())
41
              != cm::exp2(std::numeric_limits<float>::quiet_NaN()),
42
              "exp2f(NaN) == NaN");
43
static_assert(cm::exp2(200.0f)  == std::numeric_limits<float>::infinity(),
44
              "exp2f(overflow) -> +inf");
45
static_assert(cm::exp2(-200.0f) == 0.0f, "exp2f(deep underflow) -> 0");
46

47
// Float underflow shoulder: x in (-150, -149) must produce smallest subnormal.
48
constexpr float fx_subnormal = cm::exp2(-149.5f);
49
static_assert(fx_subnormal == std::numeric_limits<float>::denorm_min(),
50
              "exp2f(-149.5) rounds to smallest positive subnormal");
51
static_assert(cm::exp2(-150.0f) == 0.0f, "exp2f(-150) underflows to 0");
52

53
// Pin exact threshold cutovers to guard against < vs <= regressions.
54
static_assert(cm::exp2(-149.0f) == std::numeric_limits<float>::denorm_min(),
55
              "exp2f(-149) == smallest positive subnormal (cutover boundary)");
56
static_assert(cm::exp2(128.0f) == std::numeric_limits<float>::infinity(),
57
              "exp2f(128) saturates to +inf (cutover boundary)");
58

59
// Special values
60
static_assert(cm::exp2(std::numeric_limits<double>::infinity())
61
              == std::numeric_limits<double>::infinity(), "exp2(+inf) == +inf");
62
static_assert(cm::exp2(-std::numeric_limits<double>::infinity()) == 0.0,
63
              "exp2(-inf) == 0");
64
// Overflow / underflow
65
static_assert(cm::exp2(2000.0)  == std::numeric_limits<double>::infinity(),
66
              "exp2(2000) -> +inf");
67
static_assert(cm::exp2(-2000.0) == 0.0, "exp2(-2000) -> 0");
68
// NaN propagation
69
static_assert(cm::exp2(std::numeric_limits<double>::quiet_NaN())
70
              != cm::exp2(std::numeric_limits<double>::quiet_NaN()),
71
              "exp2(NaN) == NaN");
72

73
// Acceptance forms for issue #722 (lns full constexpr).
74
// Round-trip: exp2(log2(x)) ~= x  -- the lns encode/decode loop.
75
constexpr double rt_pi   = cm::exp2(cm::log2(3.14159265358979323846));
76
static_assert(rt_pi > 3.14159 && rt_pi < 3.14160,
77
              "exp2(log2(pi)) ~= pi");
78
constexpr double rt_e    = cm::exp2(cm::log2(2.71828182845904523536));
79
static_assert(rt_e  > 2.71828 && rt_e  < 2.71829,
80
              "exp2(log2(e)) ~= e");
81
constexpr double rt_1024 = cm::exp2(cm::log2(1024.0));
82
static_assert(rt_1024 > 1023.999999 && rt_1024 < 1024.000001,
83
              "exp2(log2(1024)) ~= 1024");
84

85
// Fractional argument: exp2(0.5) == sqrt(2) ~= 1.4142135623730951
86
constexpr double cx_sqrt2 = cm::exp2(0.5);
87
static_assert(cx_sqrt2 > 1.4142135 && cx_sqrt2 < 1.4142137,
88
              "exp2(0.5) ~= sqrt(2)");
89

90
// Underflow shoulder: x in (-1075, -1074) must produce the smallest subnormal
91
// (2^-1074) by round-to-nearest, not 0 (regression for the deferred-scale fix).
92
//   exp2(-1074.5) = 2^-1074 / sqrt(2) ~= 0.707 * 2^-1074, rounds to 2^-1074.
93
constexpr double cx_subnormal = cm::exp2(-1074.5);
94
static_assert(cx_subnormal > 0.0,
95
              "exp2(-1074.5) preserves correct rounding to smallest subnormal");
96
static_assert(cx_subnormal == std::numeric_limits<double>::denorm_min(),
97
              "exp2(-1074.5) rounds to 2^-1074 (smallest positive subnormal)");
98

99
// Floor of the saturation: at exactly -1075, x is half a ulp below the
100
// smallest subnormal -- round-to-nearest-even gives 0.
101
static_assert(cm::exp2(-1075.0) == 0.0, "exp2(-1075) underflows to 0");
102
static_assert(cm::exp2(-1100.0) == 0.0, "exp2(-1100) deep underflow == 0");
103

104
// Pin exact threshold cutovers to guard against < vs <= regressions.
105
static_assert(cm::exp2(-1074.0) == std::numeric_limits<double>::denorm_min(),
106
              "exp2(-1074) == smallest positive subnormal (cutover boundary)");
107
static_assert(cm::exp2(1024.0) == std::numeric_limits<double>::infinity(),
108
              "exp2(1024) saturates to +inf (cutover boundary)");
109

110
// ----------------------------------------------------------------------------
111
// Runtime cross-check vs std::exp2 + round-trip stress
112
// ----------------------------------------------------------------------------
113

114
int main() {
1✔
115
        std::cout << "sw::math::constexpr_math::exp2 verification\n";
1✔
116

117
        int errors = 0;
1✔
118
        auto check = [&](const char* name, double x, double our, double ref, double tol) {
41✔
119
                double err = (ref == 0.0) ? std::abs(our) : std::abs((our - ref) / ref);
41✔
120
                if (err > tol) {
41✔
NEW
121
                        ++errors;
×
122
                        std::cout << "FAIL " << name
NEW
123
                                  << "  x="   << std::setprecision(17) << x
×
NEW
124
                                  << "  our=" << our
×
NEW
125
                                  << "  ref=" << ref
×
NEW
126
                                  << "  rel-err=" << err << '\n';
×
127
                }
128
        };
42✔
129

130
        // Direct sweep against std::exp2 over a representative range, including
131
        // the denormal underflow shoulder.
132
        const double points[] = {
1✔
133
                -1074.9, -1074.5, -1074.1,                                          // shoulder
134
                -1023.5, -100.5,  -10.25, -3.5, -1.5, -0.5, -0.25, -0.0001,
135
                 0.0,    0.0001, 0.25,    0.5,  1.5,   3.5,   10.25, 100.5, 1023.5,
136
        };
137
        for (double x : points) {
21✔
138
                double our = cm::exp2(x);
20✔
139
                double ref = std::exp2(x);
20✔
140
                check("double sweep", x, our, ref, 1e-15);
20✔
141
        }
142

143
        // Round-trip stress against log2: for any positive x, exp2(log2(x)) ~= x.
144
        // This is the encode/decode loop the lns work in #722 will exercise.
145
        const double rt_points[] = {
1✔
146
                1e-100, 1e-50, 1e-10, 1e-5, 1e-3, 0.1, 0.5, 0.999, 1.0, 1.001,
147
                1.5, 2.0, 3.14, 7.0, 10.0, 100.0, 1024.0, 1e3, 1e10, 1e50, 1e100,
148
        };
149
        for (double x : rt_points) {
22✔
150
                double rt = cm::exp2(cm::log2(x));
21✔
151
                // At extreme x (~1e+/-100), log2(x) reaches ~332 in magnitude. The
152
                // derivative of 2^y amplifies absolute error in y by ln(2) * 2^y, so
153
                // even at machine-precision log2 the round-trip relative error reaches
154
                // ~5e-14 at the tails. 1e-13 leaves comfortable margin without masking
155
                // any real algorithmic regression.
156
                check("round-trip exp2(log2(x))", x, rt, x, 1e-13);
21✔
157
        }
158

159
        // Float sweep, including the denormal underflow shoulder.
160
        const float fpoints[] = {
1✔
161
                -149.9f, -149.5f, -149.1f,                                          // shoulder
162
                -127.5f, -10.25f, -3.5f, -0.5f, 0.0f, 0.5f, 3.5f, 10.25f, 127.5f,
163
        };
164
        for (float x : fpoints) {
13✔
165
                float our = cm::exp2(x);
12✔
166
                float ref = std::exp2(x);
12✔
167
                double err = (ref == 0.0f) ? std::abs(our) : std::abs((our - ref) / ref);
12✔
168
                // Float epsilon ~1.19e-7; allow 1e-6 relative.
169
                if (err > 1e-6) {
12✔
NEW
170
                        ++errors;
×
NEW
171
                        std::cout << "FAIL float sweep  x=" << x
×
NEW
172
                                  << "  our=" << our << "  ref=" << ref
×
NEW
173
                                  << "  rel-err=" << err << '\n';
×
174
                }
175
        }
176

177
        std::cout << "constexpr_math::exp2: " << (errors == 0 ? "PASS" : "FAIL")
1✔
178
                  << " (" << errors << " error" << (errors == 1 ? "" : "s") << ")\n";
1✔
179
        return (errors == 0) ? 0 : 1;
1✔
180
}
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