Skip to content

Instantly share code, notes, and snippets.

@jonocarroll
Created July 2, 2025 00:30
Show Gist options
  • Save jonocarroll/8981f3198b282b14d1622dfb23c2afe4 to your computer and use it in GitHub Desktop.
Save jonocarroll/8981f3198b282b14d1622dfb23c2afe4 to your computer and use it in GitHub Desktop.
ndigits floor(log10(abs(x))) + 1 benchmarks
## R - just vectorised (JIT) ###################################################
nd_R <- function(x) {
floor(log10(abs(x))) + 1
}
nd_R(c(123456, 234, -72))
#> [1] 6 3 2
## Fortran - {quickr} (compiled) ###############################################
nd2f <- function(x) {
declare(
type(x = double(NA))
)
floor(log10(abs(x))) + 1
}
nd_F <- quickr::quick(nd2f)
nd_F(c(123456, 234, -72))
#> [1] 6 3 2
## C - {callme} (compiled) #####################################################
callme::compile(
"
#include <R.h>
#include <Rinternals.h>
#include <math.h>
SEXP nd_C(SEXP x) {
int n = length(x);
SEXP result = PROTECT(allocVector(REALSXP, n));
double *x_ptr = REAL(x);
double *result_ptr = REAL(result);
for (int i = 0; i < n; i++) {
result_ptr[i] = floor(log10(fabs(x_ptr[i]))) + 1.0;
}
UNPROTECT(1);
return result;
}
"
)
nd_C(c(123456, 234, -72))
#> [1] 6 3 2
## C++ - {Rcpp} (compiled) #####################################################
nd_Rcpp <- Rcpp::cppFunction(
"
NumericVector floor_log10_plus1(NumericVector x) {
int n = x.size();
NumericVector result(n);
for (int i = 0; i < n; i++) {
result[i] = std::floor(std::log10(std::abs(x[i]))) + 1;
}
return result;
}
"
)
nd_Rcpp(c(123456, 234, -72))
#> [1] 6 3 2
## Rust - {rextendr} (compiled) ################################################
rextendr::rust_function(
r"(
fn nd_Rust(x: &[f64]) -> Vec<f64> {
x.iter()
.map(|&val| (val.abs().log10().floor() + 1.0))
.collect()
})",
profile = "release"
)
#> ℹ build directory: '/private/var/folders/1h/k6c5hb4d2qx07m8kfqb54f9c0000gn/T/RtmpB7CYBi/file161661a57c637'
#> ✔ Writing '/private/var/folders/1h/k6c5hb4d2qx07m8kfqb54f9c0000gn/T/RtmpB7CYBi/file161661a57c637/target/extendr_wrappers.R'
nd_Rust(c(123456, 234, -72))
#> [1] 6 3 2
## Lua - {luajr} (JIT) #########################################################
lua_f <- "function(x)
local result = {}
for i = 1, #x do
result[i] = math.floor(math.log10(math.abs(x[i]))) + 1
end
return result
end"
nd_lua <- luajr::lua_func(lua_f, "r")
unlist(nd_lua(c(123456, 234, -72)))
#> [1] 6 3 2
## Python - {reticulate} (interactive) #########################################
library(reticulate)
reticulate::py_run_string(
'
import math
def nd_python(x):
return [math.floor(math.log10(abs(val))) + 1 for val in x]
'
)
py$nd_python(c(123456, 234, -72))
#> [1] 6 3 2
## BENCHMARKS ##################################################################
set.seed(1)
nums <- round(runif(1e4, -1, 1) * 1e4)
nums <- nums[nums != 0]
JuliaCall::julia_setup(file.path(path.expand("~"), ".juliaup/bin/"))
#> Julia version 1.11.1 at location /Users/jono/.julia/juliaup/julia-1.11.1+0.aarch64.apple.darwin14/bin will be used.
#> Loading setup script for JuliaCall...
#> Finish loading setup script for JuliaCall.
JuliaCall::julia_assign("nums", as.integer(nums))
b <- bench::mark(
Python = py$nd_python(nums),
Lua = unlist(nd_lua(nums)),
Rust = nd_Rust(nums),
Julia = JuliaCall::julia_eval("ndigits.(nums)"),
`C++` = nd_Rcpp(nums),
C = nd_C(nums),
R = nd_R(nums),
Fortran = nd_F(nums),
min_iterations = 10,
check = TRUE
)
dplyr::arrange(b[, 1:8], median)
#> # A tibble: 8 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 C 21.4µs 23.8µs 41277. 78.2KB 95.2
#> 2 Fortran 23.6µs 26.2µs 37384. 82.3KB 89.9
#> 3 Rust 23µs 27.1µs 35273. 99.4KB 81.3
#> 4 C++ 25.3µs 27.6µs 35378. 83.3KB 85.1
#> 5 R 43.1µs 48µs 20681. 174.9KB 96.0
#> 6 Julia 99.6µs 107.1µs 8940. 39.1KB 10.3
#> 7 Python 405.9µs 439.4µs 2095. 39.1KB 2.01
#> 8 Lua 14.5ms 14.8ms 67.5 156.3KB 0
plot(b)
![](https://i.imgur.com/O6uv2DI.png)<!-- -->
@jonocarroll
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment