OpenLibM Integration
Custom exp/log for Windows performance in bgms. Source: src/math/custom_explog.h, custom_explog.cpp, custom_arma_explog.h, and explog_macros.h.
The problem
The std::exp() and std::log() that ship with the Windows toolchain R uses (Rtools / UCRT) are much slower than their macOS and Linux counterparts. Because MCMC samplers call exp() and log() millions of times per run, the difference makes bgms prohibitively slow on Windows without intervention.
Elapsed seconds for 100 iterations over 1 000 000 elements (GitHub Actions runners, 2026-04-02):
| macOS | Windows | |
|---|---|---|
exp() (base R) |
0.999s | 4.22s |
MY_EXP (custom) |
1.065s | 1.32s |
log() (base R) |
0.695s | 5.8s |
MY_LOG (custom) |
0.849s | 1.01s |
library(bgms)
bgms:::get_explog_switch()
x = rnorm(1e6)
n = 100L
system.time(for(i in seq_len(n)) exp(x))
system.time(for(i in seq_len(n)) bgms:::rcpp_ieee754_exp(x))
system.time(for(i in seq_len(n)) log(abs(x)))
system.time(for(i in seq_len(n)) bgms:::rcpp_ieee754_log(abs(x)))OpenLibM solution
bgms ships a copy of the __ieee754_exp() and __ieee754_log() implementations from OpenLibM (the FreeBSD/Sun math library). These are self-contained C functions that bypass the toolchain’s standard library, bringing Windows performance much closer to macOS and Linux.
The implementation in custom_explog.cpp (approximately 800 lines) is the original Sun/FreeBSD code, lightly adapted to compile as C++ within an R package.
Platform detection
The macro system in explog_macros.h controls when custom exp/log is used:
#if (defined(_WIN32) && (!defined(CUSTOM_EXP_LOG) || CUSTOM_EXP_LOG == 0)) \
|| (!defined(_WIN32) && defined(CUSTOM_EXP_LOG) && CUSTOM_EXP_LOG != 0)
#define USE_CUSTOM_LOG 1
#endifThe logic:
- Windows — Uses custom exp/log by default because the Rtools toolchain’s
std::exp/std::logare much slower - Linux/macOS — Uses the system libm by default (which is fast). Custom exp/log can be enabled by setting
CUSTOM_EXP_LOG=1
The configure and configure.win scripts do not control this flag. Platform detection is handled entirely by the C++ preprocessor via _WIN32. Users can override the default by adding -DCUSTOM_EXP_LOG=1 (to enable custom on non-Windows) or by omitting the flag (to keep the platform default).
Usage in C++ code
Two levels of macros are available:
| Macro | Expands to (custom) | Expands to (system) |
|---|---|---|
MY_EXP(x) |
__ieee754_exp(x) |
std::exp(x) |
MY_LOG(x) |
__ieee754_log(x) |
std::log(x) |
ARMA_MY_EXP(X) |
custom_arma_exp(X) |
arma::exp(X) |
ARMA_MY_LOG(X) |
custom_arma_log(X) |
arma::log(X) |
The ARMA_MY_EXP variant applies element-wise custom exp/log to Armadillo matrices and vectors. It wraps custom_arma_exp() and custom_arma_log() from custom_arma_explog.h, which iterate over matrix elements and call the scalar __ieee754_exp/__ieee754_log.
Performance
On Windows, the Rtools std::exp/std::log are much slower than the OpenLibM replacements, making the custom path essential for acceptable MCMC runtimes. bgms enables the custom path on Windows by default.
On macOS and Linux, the system libm is already fast, so bgms uses the system implementations by default. The custom path can be enabled for cross-platform reproducibility, at the cost of slightly slower scalar operations.
EXP_BOUND and overflow prevention
The variable helpers use EXP_BOUND = 709 as the threshold for safe exponentiation, derived from the IEEE 754 double-precision range:
- \(\exp(709) \approx 8.2 \times 10^{307}\) — largest representable
- \(\exp(710) = +\text{Inf}\)
- \(\exp(-745) \approx 5 \times 10^{-324}\) — smallest positive subnormal
- \(\exp(-746) = 0\)
This bound is used throughout the codebase (not just in variable helpers) to guard against overflow before calling MY_EXP().
R interface
explog_interface.cpp (in src/, not src/math/) exposes three functions to R:
| R function | Purpose |
|---|---|
get_explog_switch() |
Returns "custom" or "standard" depending on compile-time USE_CUSTOM_LOG |
rcpp_ieee754_exp(x) |
Vectorised exp via MY_EXP |
rcpp_ieee754_log(x) |
Vectorised log via MY_LOG |
On the standard path, rcpp_ieee754_exp() and rcpp_ieee754_log() call std::exp and std::log despite the ieee754 naming.
The explog_interface.cpp file exposes the custom exp/log functions to R for testing purposes. This allows verification that the custom implementations match expected values in R-level unit tests.