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

stillwater-sc / universal / 24948080228

26 Apr 2026 04:19AM UTC coverage: 84.337% (-0.02%) from 84.354%
24948080228

push

github

web-flow
feat(math): add constexpr log (natural logarithm) to sw::math::constexpr_math (#771)

* feat(math): add constexpr log (natural logarithm) to sw::math::constexpr_math

Adds constexpr<float|double> log to the constexpr_math facility (#763
Epic, #423/PR #762 substrate). Tier-2 sub-issue: natural log used by
dbns and useful for general literal construction.

Implementation: log(x) = log2(x) * ln(2) -- thin wrapper over the
existing log2 machinery. Special cases (NaN, x<0, 0, +inf) are handled
explicitly before the multiply rather than relying on log2 to propagate
them; clang's stricter constexpr evaluator rejects "arithmetic produces
a NaN" so log2(NaN) * LN2 would trip the diagnostic even though log2
itself returned NaN cleanly.

Verification:
  - 9 static_asserts: log(1) == 0, log(2) ~= ln(2) (LN2 anchor),
    log(e) ~= 1, log(10) ~= ln(10), log(8) == 3*log(2) cross-check,
    plus NaN / +/-inf / negative-input special values
  - Runtime sweep over 22 hand-picked points spanning 1e-300 to 1e300
  - Float sweep over 10 points

Out-of-band accuracy stress (100K random samples in [1e-100, 1e100]):
  - max relative error 1.29e-16 = 0.58 x double epsilon (sub-ulp)
  - tighter than the standalone log2 because the LN2 multiply often
    rounds favorably for non-pow-2 inputs

cm_log, cm_log2, cm_exp2 all PASS on gcc and clang in build_ci /
build_ci_clang.

Resolves #766
Relates to #763 (Epic)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(math): pin log(-0) and use tolerance for log cross-check (CodeRabbit)

Two Nitpicks from CodeRabbit on PR #771:

1. Pin -0 behavior explicitly: cm::log(-0.0) and cm::log(-0.0f) must
   return -infinity (the IEEE-754 zero branch matches both signed
   zeros). The implementation already handled this correctly via
   x == 0.0 (which compares equal for both +0 and -0), but a regression
   test prevents accidental breakage.

2. Replace exact-equality cross-check (log(8) == 3*log(2)) with a
   toleranc... (continued)

33 of 42 new or added lines in 2 files covered. (78.57%)

8 existing lines in 1 file now uncovered.

45239 of 53641 relevant lines covered (84.34%)

6385310.1 hits per line

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

70.0
/internal/constexpr_math/api/log.cpp
1
// log.cpp: regression for sw::math::constexpr_math::log (natural logarithm)
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
// Anchors derivable from the algorithm: log(1) = 0 (independent of implementation).
22
static_assert(cm::log(1.0)  == 0.0, "log(1) == 0");
23
static_assert(cm::log(1.0f) == 0.0f, "logf(1) == 0");
24

25
// log(2) ~= ln(2) ~= 0.6931471805599453 (the LN2 constant itself)
26
constexpr double cx_log2 = cm::log(2.0);
27
static_assert(cx_log2 > 0.69314718 && cx_log2 < 0.69314719,
28
              "log(2) ~= 0.6931471805599453");
29

30
// log(e) ~= 1 (within ~1 ulp transcendental composition error)
31
constexpr double cx_loge = cm::log(2.71828182845904523536);
32
static_assert(cx_loge > 0.99999999 && cx_loge < 1.00000001,
33
              "log(e) ~= 1");
34

35
// log(10) ~= 2.302585092994046
36
constexpr double cx_log10 = cm::log(10.0);
37
static_assert(cx_log10 > 2.3025850 && cx_log10 < 2.3025851,
38
              "log(10) ~= 2.302585092994046");
39

40
// Cross-check: log(8) ~= log(2) * 3 (within a few ulp).
41
// Tolerance instead of exact equality so a future refactor of log2's internal
42
// math cannot break this regression for a sub-ulp reason.
43
constexpr double cx_log8 = cm::log(8.0);
44
constexpr double cx_3log2 = 3.0 * cm::log(2.0);
45
constexpr double cx_delta = (cx_log8 > cx_3log2) ? (cx_log8 - cx_3log2)
46
                                                  : (cx_3log2 - cx_log8);
47
static_assert(cx_delta < 1e-15, "log(8) ~= 3 * log(2)");
48

49
// Special values (inherited from log2)
50
static_assert(cm::log(0.0) == -std::numeric_limits<double>::infinity(),
51
              "log(0) == -inf");
52
static_assert(cm::log(-0.0) == -std::numeric_limits<double>::infinity(),
53
              "log(-0) == -inf (signed zero hits the same branch)");
54
static_assert(cm::log(0.0f) == -std::numeric_limits<float>::infinity(),
55
              "logf(0) == -inf");
56
static_assert(cm::log(-0.0f) == -std::numeric_limits<float>::infinity(),
57
              "logf(-0) == -inf");
58
static_assert(cm::log(std::numeric_limits<double>::infinity())
59
              == std::numeric_limits<double>::infinity(),
60
              "log(+inf) == +inf");
61
static_assert(cm::log(-1.0) != cm::log(-1.0), "log(-1) == NaN");
62
static_assert(cm::log(-2.5f) != cm::log(-2.5f), "logf(-2.5) == NaN");
63
static_assert(cm::log(std::numeric_limits<double>::quiet_NaN())
64
              != cm::log(std::numeric_limits<double>::quiet_NaN()),
65
              "log(NaN) == NaN");
66

67
// ============================================================================
68
// Runtime cross-check vs std::log
69
// ============================================================================
70

71
int main() {
1✔
72
        std::cout << "sw::math::constexpr_math::log verification\n";
1✔
73

74
        int errors = 0;
1✔
75
        auto check = [&](const char* name, double x, double our, double ref, double tol) {
22✔
76
                double err = (ref == 0.0) ? std::abs(our) : std::abs((our - ref) / ref);
22✔
77
                if (err > tol) {
22✔
NEW
78
                        ++errors;
×
79
                        std::cout << "FAIL " << name
NEW
80
                                  << "  x=" << std::setprecision(17) << x
×
NEW
81
                                  << "  our=" << our
×
NEW
82
                                  << "  ref=" << ref
×
NEW
83
                                  << "  rel-err=" << err << '\n';
×
84
                }
85
        };
23✔
86

87
        // Sweep over a representative dynamic range.
88
        const double points[] = {
1✔
89
                1e-300, 1e-100, 1e-10, 1e-5, 0.001, 0.1, 0.5, 0.999, 1.0, 1.001,
90
                1.5, 2.0, 2.71828182845904523536, 3.14159265358979323846,
91
                7.0, 10.0, 100.0, 1024.0, 1e3, 1e10, 1e100, 1e300,
92
        };
93
        for (double x : points) {
23✔
94
                double our = cm::log(x);
22✔
95
                double ref = std::log(x);
22✔
96
                check("double sweep", x, our, ref, 1e-15);
22✔
97
        }
98

99
        // Float sweep
100
        const float fpoints[] = {
1✔
101
                1e-30f, 1e-10f, 0.001f, 0.5f, 1.0f, 2.0f, 2.71828183f, 10.0f, 1e10f, 1e30f,
102
        };
103
        for (float x : fpoints) {
11✔
104
                float our = cm::log(x);
10✔
105
                float ref = std::log(x);
10✔
106
                double err = (ref == 0.0f) ? std::abs(our) : std::abs((our - ref) / ref);
10✔
107
                if (err > 1e-6) {
10✔
NEW
108
                        ++errors;
×
NEW
109
                        std::cout << "FAIL float sweep  x=" << x
×
NEW
110
                                  << "  our=" << our << "  ref=" << ref
×
NEW
111
                                  << "  rel-err=" << err << '\n';
×
112
                }
113
        }
114

115
        std::cout << "constexpr_math::log: " << (errors == 0 ? "PASS" : "FAIL")
1✔
116
                  << " (" << errors << " error" << (errors == 1 ? "" : "s") << ")\n";
1✔
117
        return (errors == 0) ? 0 : 1;
1✔
118
}
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