library(round) ## Utilities (sync with ../man/roundX.Rd ! ) ##' maybe add to package -- it's really useful here : formatF <- function(x, ...) noquote(format(x, scientific=FALSE, drop0trailing=TRUE, ...)) same_cols <- function(m) all(m == m[,1]) matPm <- function(y) { matplot(y=y, type = "l", lwd = 2, xlab = "i", ylab = deparse(substitute(y))) abline(h = 0, lty=2, col="gray") legend("topleft", legend = setdiff(roundVersions, "r3"), col = 1:6, lty = 1:5, lwd = 2, bty = "n") } str(.M <- .Machine, digits = 8) capabilities() str(.M[grep("^sizeof", names(.M))]) ## also differentiate long-double.. (b64 <- .M$sizeof.pointer == 8) (arch <- Sys.info()[["machine"]]) (onWindows <- .Platform$OS.type == "windows") (hasLD <- unname(capabilities("long.double"))) (validLD <- hasLD && (.M$longdouble.digits > .M$double.digits) && (.M$longdouble.min.exp <= 8L * .M$double.min.exp)) win32 <- onWindows && !b64 doExtras <- round:::doExtras() if(!dev.interactive(orNone=TRUE)) pdf("round-tst.pdf") options(width = 150) # for wide "tables" dd <- 0:12 x55 <- 55 + as.numeric(vapply(dd+1, function(k) paste0(".", strrep("5",k)), "")) rnd.x <- roundAll(x55, dd) formatF(rnd.x, digits = 14) ## and the deviations from "r3d.C": noquote(cbind(dig = format(dd), format(rnd.x - rnd.x[,"r3d.C"], zero.print = ".", digits = 4))) ## FIXME: test ! ## Had a bug in "r3d.C we don't want --- are getting into denormalized regions! xx <- c(7e-304 , pi*10^-(300:323) , 5e-324) # = 4.94..e-324, the 'mm' number dig <- 305:340 okD <- dig <= 323L (typV <- setdiff(roundVersions, c("r0.C", "r1.C"))) (no_printf <- setdiff(roundVersions, "sprintf")) (no_LDOUBLE <- setdiff(roundVersions, "r3.C")) # BDR's valgrind-ed version has NaN here (for 7e-304) epsC <- .Machine$double.eps for(x in xx) { cat("\n", x, ":\n") dx <- x - roundAll(x, dig) print(cbind(dig = dig, "d+l10" = dig + .30103*(ilogb(x) + .5), "|dx|/x" = abs(dx)/x)[okD,], digits = 4) ## FIXME: can do much more rational tests: err |dx| =~ 10^-dd (if dd > 0) if(validLD) { if(x == 7e-304) { ## denormalized etc stopifnot(abs(dx) < 10* x * epsC) } else stopifnot(is.finite(dx)) stopifnot(dx[!okD, typV] == 0) } else { # no 'long double' -- Lnx 64b: interestingly "sprintf" is quite a bit affected stopifnot(is.finite(dx[,no_LDOUBLE])) cat("No (proper) long_double capability\n") ## FIXME .. apart from "sprintf" things look good ... "yeah.." ## ----- TODO derive from above print( .... abs(dx)/x) if(FALSE) stopifnot(abs(dx/x)[!okD, no_printf] < 10 * epsC) } } ###--- keep in sync with ../man/roundX.Rd ------------------ ## There now have "exhaustive small digit cases" ## where the conclusion could be that "r1" is best ### More studies -- Study of small digits cases ---------------------------------------- ## 1. all numbers in [0,1] set.seed(47) I <- c(0:999, randI(1000, 4), randI(2000, 5), randI(3000, 6)) ndI <- 1L + as.integer(log10(pmax(1,I))) # number of (decimal) digits of I Ix <- I + 0.5 x <- Ix / (10^ndI) # numbers to be rounded; all of the form .5 SM <- roundAll(x, digits = ndI) print(cbind(x, ndI, SM)[1:100,], digits = 9) ## sprintf *is* quite different from the r[12]* R versions ... ## Error, assuming "r3" to be best, as it *does* really go to nearest: errM <- SM - SM[,"r3"] rbind(exact = colSums(err0 <- errM != 0) , # (can have very small irrelevant differences) relevant= colSums(errT <- abs(errM) > .001*10^-ndI)) # --> sprintf clearly 2nd best (has changed +1) ## For F30 Linux 64-bit (gcc): ## sprintf r0.C r1.C r1a.C r2.C r2a.C r3.C r3d.C r3 ## exact 1494 1829 1829 1829 1829 1829 2 0 0 ## relevant 1493 1828 1828 1828 1828 1828 1 0 0 ## The middle round() versions ("r0*", "r1*", "r2*") all give the same (here, for x in [0,1]): (ver012 <- grep("^r[012]", roundVersions, value=TRUE)) stopifnot(same_cols(errM[, ver012])) ## Visualization of error happening cumErr <- apply(errT[,colnames(SM) != "r3"], 2L, cumsum) matPm(cumErr) sp <- search() if(require("Matrix")) { showSp <- function(m) print(image(as(m, "sparseMatrix"), aspect = 4, ## fails, bug in lattice? useRaster = !dev.interactive(TRUE) && (nrow(m) >= 2^12), border.col = if(nrow(m) < 1e3) adjustcolor(1, 1/2) else NA)) showSp(errM[1:100,]) showSp(errM) showSp(errM != 0) } ## 2. Numbers of varying sizes : n <- length(I) n1 <- round(n*.4) n2 <- n - n1 set.seed(7) dp <- sample(c(sample( -2:2, n1, replace=TRUE), sample(c(-1,1), n2, replace=TRUE) * rpois(n2, lambda = pi))) stopifnot(length(dp) == n) nd2 <- ndI + dp nd2[nd2 == 0] <- 1 + rpois(sum(nd2 == 0), 0.5) x <- Ix / (10^nd2) S2 <- roundAll(x, digits = nd2) # --> warning "NAs introduced by coercion" ## "Error", assuming "r3" to be best, as it *does* really go to nearest: dP <- nd2 >= 0 # as negative digits don't work for "sprintf" err2 <- S2[dP,] - S2[dP, "r3"] rbind(exact = colSums(err0 <- err2 != 0), # (not quite correct) relevant= colSums(err2T <- abs(err2) > .01*10^-nd2[dP])) # --> sprintf is *worst* ! ## For F30 Linux 64-bit (gcc): ## sprintf r0.C r1.C r1a.C r2.C r2a.C r3.C r3d.C r3 ## exact 1639 1454 1503 1503 1454 1454 1 0 0 ## relevant 1638 1453 1502 1502 1453 1453 0 0 0 ## Visualization of error happening cumEr2 <- apply(err2T, 2L, cumsum) matPm(cumEr2[,colnames(S2) != "r3"]) ## Really 4 distinct groups: r0-r2* || r1* || sprintf || r3* plot(hclust(dist(t(cumEr2)/1e6, "manhattan"))) if(require("Matrix")) { showSp(err2[1:100,]) showSp(err2) showSp(err2 != 0) } stopifnot(same_cols(err2[, c("r0.C", "r2.C", "r2a.C")])) ## The "3C" versions *not* identical to the last bit but stopifnot(same_cols(err2T[, c("r3.C", "r3d.C")])) # the "3C" versions are equivalent ## 3. Even more "widely varying" sizes and digits: set.seed(43) I <- c(0:9999, randI(2000, 6), randI(8000, 8), randI(20000, 11), randI(40000, 12)) ndI <- 1L + as.integer(log10(pmax(1,I))) # number of (decimal) digits of I Ix <- I + 0.5 n <- length(I) n1 <- round(n*.4) n2 <- n - n1 ## length(I) =: n == n1+n2 set.seed(7) dp <- sample(c(sample(-2:2, n1, replace=TRUE), sample(c(-1,1), n2, replace=TRUE) * rpois(n2, lambda = 7))) stopifnot(length(dp) == n) nd3 <- ndI + dp ## nd3 := #{digits} to round to .. should all be >= 1 : nd3[nd3 == 0] <- 1 + rpois(sum(nd3 == 0), 0.5) x <- Ix / (10^nd3) S3 <- roundAll(x, digits = nd3) # --> warnings ... ## err3 := "Error", assuming "r3" to be correct .. dP <- nd3 >= 0 # as negative digits don't work for "sprintf" err3 <- S3[dP,] - S3[dP, "r3"] rbind(exact = colSums(err3. <- err3 != 0), # (not quite correct) relevant= colSums(err3T <- abs(err3) > .01*10^-nd3[dP])) # --> sprintf is *worst* ! ## For F30 Linux 64-bit (gcc): ## sprintf r0.C r1.C r1a.C r2.C r2a.C r3.C r3d.C r3 ## exact 18958.17108 17512 17512 17108 17108 752 688 0 (earlier 2020?) ## exact 18885 17108 17512 17512 17108 17108 752 688 0 ## relevant 18609 16762 17166 17166 16762 16762 464 357 0 ## "r0*" and "r2*" are the same (here), and so are the two "r1*" : stopifnot(same_cols(err3[, vg1 <- c("r0.C", "r2.C", "r2a.C")]), same_cols(err3[, vg2 <- c("r1.C", "r1a.C")])) ## Visualization of error happening cumEr3 <- apply(err3T, 2L, cumsum) matPm(cumEr3[,colnames(S3) != "r3"]) plot(hclust(dist(t(cumEr3)/1e6, "manhattan"))) if(require("Matrix")) { showSp(err3[1:100,]) # 0:99 showSp(err3[1:1000,]) # 0:999 showSp(err3) showSp(err3 != 0) # B/W version of the above } ## if(!any(sp == "package:Matrix")) detach("package:Matrix") if(doExtras && require("gmp")) { ## version from ../vignettes/rationalRound.Rmd ## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if(print(packageVersion("gmp")) < "0.6-1") { ## From 'gmp's namespace, usually "hidden", needed here : is.whole.bigq <- gmp:::is.whole.bigq biginteger_mod <- gmp:::biginteger_mod .mod.bigz <- function(e1, e2) .Call(biginteger_mod, e1, e2) ##' rounding to integer a la "nearbyint()" -- i.e. "round to even" round0 <- function(x) { nU <- as.bigz.bigq(xU <- x + as.bigq(1, 2)) # traditional round: .5 rounded up if(any(I <- is.whole.bigq(xU))) { # I <==> x == .5 : "hard case" I[I] <- .mod.bigz(nU[I], 2L) == 1L # rounded up is odd ==> round *down* nU[I] <- nU[I] - 1L } nU } roundQ <- function(x, digits = 0, r0 = round0) { ## round(x * 10^d) / 10^d p10 <- as.bigz(10) ^ digits r0(x * p10) / p10 } } roundAllX <- function(x, digits, versions = roundVersions) { M <- cbind(roundAll(x, digits, versions=versions), xctQ = asNumeric(roundQ(as.bigq(x), digits))) rownames(M) <- format(x) M } S3q <- roundAllX(x, digits = nd3) # --> warnings ... if(length(w <- warnings())) print(summary(w)) ## err3q := "Error", assuming "xctQ" to be correct .. dP <- nd3 >= 0 # as negative digits don't work for "sprintf" err3q <- S3q[dP,] - S3q[dP, "xctQ"] print( rbind(exact = colSums( err3q != 0), relevant= colSums(err3qT <- abs(err3q) > .01*10^-nd3[dP])) ) ## For F30 Linux 64-bit (gcc): ## sprintf r0.C r1.C r1a.C r2.C r2a.C r3.C r3d.C r3 ## ............... ## "r0*" and "r2*" are the same (here), and so are the two "r1*" : stopifnot(same_cols(err3q[, vg1]), same_cols(err3q[, vg2])) ## Visualization of error happening cumEr3q <- apply(err3qT, 2L, cumsum) matPm(cumEr3q[,colnames(S3q) != "xctQ"]) mtext('Comparison with "exact rational" ("xctQ") :') plot(hclust(dist(t(cumEr3q)/1e6, "manhattan"))) ## if(!any(sp == "package:gmp")) detach("package:gmp") } ## digits < 0, large |digits| ---- was wrong in R <= 3.6.x --- M <- .Machine$double.xmax options(digits=4) round(M , -(1:400)) # the horror for R <= 3.6.x: many 'Inf' (early on); ending in all NaN; ## for R >= 4.0.0, now ends in {1.797, 1.79, 1.7, 1}e308 0 0 0 ... 0 round(M/2, -(0:320)) # much better for R <= 3.6; still ending in NaN round(3141.5, -(0:320)) # ditto : "only" NaN ## much of this is tested now in /tests/reg-tests-1d.R, ~line 3666