Skip to content

Commit 3d3a46c

Browse files
Handle log2, log10 more robustly (#355)
* Gemini: log2, log10 handling improvement * Gemini: Inline LOG264/LOG1064 in logbase to preserve original function ordering * Gemini: Add explanatory comments for logarithm routines and macros * Improve comments * delint * Fine-tune NEWS * Gemini: Add test coverage for general base vector and scalar cases in log.integer64
1 parent 08b21df commit 3d3a46c

4 files changed

Lines changed: 55 additions & 9 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
1. `as.list()` doesn't overflow the protection stack for integer64 inputs (#345). Thanks @NicChr for the report and fix.
2020

21+
1. `log()` operations, especially for `log(x, base=10)` and `log(x, base=2)`, have better precision and consistency (#180).
22+
2123
## NOTES
2224

2325
1. The R version dependency has been bumped from 3.5.0 (2018) to 3.6.0 (2019).

src/integer64.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ SEXP sqrt_integer64(SEXP e1_, SEXP ret_){
467467
return ret_;
468468
}
469469

470+
// Natural logarithm: called when base is NULL (i.e. log(x)).
470471
SEXP log_integer64(SEXP e1_, SEXP ret_){
471472
long long i, n = LENGTH(ret_);
472473
long long * e1 = (long long *) REAL(e1_);
@@ -479,6 +480,8 @@ SEXP log_integer64(SEXP e1_, SEXP ret_){
479480
return ret_;
480481
}
481482

483+
// Vector base: called when length(base) > 1 (e.g. log(x, base=c(2, 10))).
484+
// Recycles x and base; dispatches to LOG264/LOG1064 if base element is 2 or 10.
482485
SEXP logvect_integer64(SEXP e1_, SEXP e2_, SEXP ret_){
483486
long long i, n = LENGTH(ret_);
484487
long long i1, n1 = LENGTH(e1_);
@@ -488,20 +491,39 @@ SEXP logvect_integer64(SEXP e1_, SEXP e2_, SEXP ret_){
488491
double * ret = REAL(ret_);
489492
Rboolean naflag = FALSE;
490493
mod_iterate(n1, n2, i1, i2) {
491-
LOGVECT64(e1[i], e2[i], ret[i], naflag)
494+
if (e2[i2] == 2.0) {
495+
LOG264(e1[i1], ret[i], naflag)
496+
} else if (e2[i2] == 10.0) {
497+
LOG1064(e1[i1], ret[i], naflag)
498+
} else {
499+
LOGVECT64(e1[i1], e2[i2], ret[i], naflag)
500+
}
492501
}
493502
if (naflag) warning(INTEGER64_NAN_CREATED_WARNING);
494503
return ret_;
495504
}
496505

506+
// Scalar base: called when length(base) == 1 (e.g. log(x, base=2), log(x, base=10)).
507+
// Dispatches to LOG264/LOG1064 (matching base R's logbase), or precomputes logl(base) once.
497508
SEXP logbase_integer64(SEXP e1_, SEXP base_, SEXP ret_){
498509
long long i, n = LENGTH(ret_);
499510
long long * e1 = (long long *) REAL(e1_);
500-
long double logbase = (long double) log(asReal(base_));
511+
double base = asReal(base_);
501512
double * ret = REAL(ret_);
502-
Rboolean naflag = (asReal(base_)>0) ? FALSE : TRUE;
503-
for(i=0; i<n; i++) {
504-
LOGBASE64(e1[i], logbase, ret[i], naflag)
513+
Rboolean naflag = (base > 0) ? FALSE : TRUE;
514+
if (base == 2.0) {
515+
for(i=0; i<n; i++) {
516+
LOG264(e1[i], ret[i], naflag)
517+
}
518+
} else if (base == 10.0) {
519+
for(i=0; i<n; i++) {
520+
LOG1064(e1[i], ret[i], naflag)
521+
}
522+
} else {
523+
long double logbase = (long double) logl((long double)base);
524+
for(i=0; i<n; i++) {
525+
LOGBASE64(e1[i], logbase, ret[i], naflag)
526+
}
505527
}
506528
if (naflag) warning(INTEGER64_NAN_CREATED_WARNING);
507529
return ret_;

src/integer64.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ else { \
225225
else { \
226226
if (e1 < 0) \
227227
naflag = TRUE; \
228-
ret = (double) sqrt((long double)e1); \
228+
ret = (double) sqrtl((long double)e1); \
229229
}
230230

231231
#define LOG64(e1, ret, naflag) \
@@ -237,11 +237,12 @@ else { \
237237
naflag = TRUE; \
238238
}
239239

240+
// NB: cast to double _after_ dividing in 'long double' for max precision.
240241
#define LOGVECT64(e1, e2, ret, naflag) \
241242
if (e1 == NA_INTEGER64) \
242243
ret = NA_REAL; \
243244
else { \
244-
ret = (double) logl((long double)e1) / log(e2); \
245+
ret = (double) (logl((long double)e1) / logl((long double)e2)); \
245246
if (isnan(ret)) \
246247
naflag = TRUE; \
247248
}
@@ -250,7 +251,7 @@ else { \
250251
if (e1 == NA_INTEGER64) \
251252
ret = NA_REAL; \
252253
else { \
253-
ret = (double) logl((long double)e1) / e2; \
254+
ret = (double) (logl((long double)e1) / e2); \
254255
if (isnan(ret)) \
255256
naflag = TRUE; \
256257
}

tests/testthat/test-integer64.R

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,13 @@ test_that("arithmetic & basic math works", {
336336
expect_identical(sqrt(as.integer64(c(0L, 1L, 4L, 9L))), as.numeric(0:3))
337337
expect_identical(log(x), log(as.numeric(x)))
338338
expect_identical(log(as.integer64(c(1L, 2L, 4L, 8L)), base=2L), as.numeric(0:3))
339+
expect_identical(log(as.integer64(c(1L, 2L, 4L, 8L)), base=c(2L, 2L, 2L, 2L)), as.numeric(0:3))
340+
expect_identical(log(as.integer64(c(8L, 1000L)), base=c(2L, 10L)), c(3.0, 3.0))
341+
expect_identical(log(as.integer64(c(8L, 27L, 1000L, 25L)), base=c(2L, 3L, 10L, 5L)), c(3.0, 3.0, 3.0, 2.0))
342+
expect_identical(log(as.integer64(c(1L, 3L, 9L, 27L)), base=3L), as.numeric(0:3))
339343
expect_identical(log2(as.integer64(c(1L, 2L, 4L, 8L))), as.numeric(0:3))
340-
expect_identical(log10(as.integer64(c(1L, 10L, 100L, 1000L))), as.numeric(0:3), tolerance=1e-7)
344+
expect_identical(log10(as.integer64(c(1L, 10L, 100L, 1000L))), as.numeric(0:3))
345+
expect_identical(log(as.integer64(c(1L, 10L, 100L, 1000L)), base=10L), as.numeric(0:3))
341346

342347
expect_identical(trunc(x), x)
343348
expect_identical(floor(x), x)
@@ -346,6 +351,19 @@ test_that("arithmetic & basic math works", {
346351
expect_identical(round(x), x)
347352

348353
expect_identical(round(x, -1L), as.integer64(rep(c(0L, 10L), each=5L)))
354+
# Halfway round-to-even tests
355+
expect_identical(
356+
round(as.integer64(c(-25L, -15L, -5L, 5L, 15L, 25L)), -1L),
357+
as.integer64(c(-20L, -20L, 0L, 0L, 20L, 20L))
358+
)
359+
expect_identical(
360+
round(as.integer64(c(-250L, -150L, -50L, 50L, 150L, 250L)), -2L),
361+
as.integer64(c(-200L, -200L, 0L, 0L, 200L, 200L))
362+
)
363+
expect_identical(
364+
round(as.integer64(c(-2500L, -1500L, -500L, 500L, 1500L, 2500L)), -3L),
365+
as.integer64(c(-2000L, -2000L, 0L, 0L, 2000L, 2000L))
366+
)
349367
})
350368

351369
test_that("basic statistics work", {
@@ -757,6 +775,9 @@ local({
757775
with_parameters_test_that(
758776
"Old \\dontshow{} tests in ?format.integer64 continue working",
759777
{
778+
# base::round() for negative digits uses floating-point approximations in fround.c
779+
# which can fail round-half-to-even on platforms with x87 FPUs (e.g. i686) (#180).
780+
skip_if(s < 0L && round(5.0 * 10.0^(-s - 1L), s) != 0.0, "base::round() does not round to even on this platform")
760781
r <- as.integer64(round(as.integer(i), s))
761782
r64 <- round(as.integer64(i), s)
762783
expect_identical(r, r64)

0 commit comments

Comments
 (0)