8

It is possible to round results into two significant digits using signif:

> signif(12500,2)
[1] 12000
> signif(12501,2)
[1] 13000

But are there an equally handy functions, like the fictitious functions below signif.floor and signif.ceiling, so that I could get two or more significant digits with flooring or ceiling?

> signif.ceiling(12500,2)
[1] 13000
> signif.floor(12501,2)
[1] 12000

EDIT:

The existing signif function works with negative numbers and decimal numbers.

Therefore, the possible solution would preferably work also with negative numbers:

> signif(-125,2)
[1] -120
> signif.floor(-125,2)
[1] -130

and decimal numbers:

> signif(1.23,2)
[1] 1.2
> signif.ceiling(1.23,2)
[1] 1.3

As a special case, also 0 should return 0:

> signif.floor(0,2)
[1] 0
Heikki
  • 2,214
  • 19
  • 34
  • This is not a duplicate of https://stackoverflow.com/questions/16463945/transforming-numbers-in-r/16464102#16464102 which considers only one significant digit. – Heikki Nov 08 '17 at 10:30

2 Answers2

11

I think this approach is proper for all types of numbers (i.e. integers, negative, decimal).

The floor function

signif.floor <- function(x, n){
  pow <- floor( log10( abs(x) ) ) + 1 - n
  y <- floor(x / 10 ^ pow) * 10^pow
  # handle the x = 0 case
  y[x==0] <- 0
  y
}

The ceiling function

signif.ceiling <- function(x, n){
  pow <- floor( log10( abs(x) ) ) + 1 - n
  y <- ceiling(x / 10 ^ pow) * 10^pow
  # handle the x = 0 case
  y[x==0] <- 0
  y
}

They both do the same thing. First count the number of digits, next use the standard floor/ceiling function. Check if it works for you.

Edit 1 Added the handler for the case of x = 0 as suggested in the comments by Heikki.

Edit 2 Again following Heikki I add some examples:

Testing different values of x

# for negative values
> values <- -0.12151 * 10^(0:4); values
# [1]    -0.12151    -1.21510   -12.15100  -121.51000 -1215.10000
> sapply(values, function(x) signif.floor(x, 2))
# [1]    -0.13    -1.30   -13.00  -130.00 -1300.00
> sapply(values, function(x) signif.ceiling(x, 2))
# [1]    -0.12    -1.20   -12.00  -120.00 -1200.00

# for positive values
> sapply(-values, function(x) signif.floor(x, 2))
# [1]    0.12    1.20   12.00  120.00 1200.00
> sapply(-values, function(x) signif.ceiling(x, 2))
# [1]    0.13    1.30   13.00  130.00 1300.00

Testing different values of n

> sapply(1:5, function(n) signif.floor(-121.51,n))
# [1] -200.00 -130.00 -122.00 -121.60 -121.51
> sapply(1:5, function(n) signif.ceiling(-121.51,n))
# [1] -100.00 -120.00 -121.00 -121.50 -121.51
storaged
  • 1,837
  • 20
  • 34
  • Seem to work quite nicely, but `signif.floor(0,2)` returns `NaN`. – Heikki Nov 08 '17 at 11:37
  • 1
    Could you edit the answer so that ít takes zeros into account and then I could accept it: after `y <- floor(x / 10 ^ pow) * 10^pow` check zeros `y[x==0] <- 0` and `return(y)` – Heikki Nov 08 '17 at 12:01
  • Cheers, You are absolutely right! I missed the border case. I think it is ok now – storaged Nov 08 '17 at 12:05
  • Another edit request: in `signif.ceiling` there should be `pow <- floor( log10( abs(x) ) ) + 1 - n` and `y <- ceiling(x / 10 ^ pow) * 10^pow` – Heikki Nov 08 '17 at 12:06
  • 1
    Works now perfectly with the following test cases: `signif.floor(c(1234,12.34,125,0.125,0,-12.5,-12.51),2)` and `signif.ceiling(c(1234,12.34,125,0.125,0,-12.5,-12.51),2)` and `signif.floor(matrix(1,2,2),2)`. – Heikki Nov 08 '17 at 12:12
  • @Heikki I added examples for others in the main answer – storaged Nov 08 '17 at 12:32
0

Edit Nowhere near as nice as @storaged's answer, but I'd started so I might as well finish:

Basically runs through each case (positive, negative, decimal or not)

signif.floor=function(x,n){
  if(x==0)(out=0)
  if(x%%round(x)==0 & sign(x)==1){out=as.numeric(paste0(el(strsplit(as.character(x),''))[1:n],collapse=''))*10^(nchar(x)-n)}
  if(x%%round(x) >0 & sign(x)==1){out=as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+1)],collapse=''))}
  if(x%%round(x)==0 & sign(x)==-1){out=(as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+1)],collapse=''))-1)*10^(nchar(x)-n-1)}
  if(x%%round(x) <0 & sign(x)==-1){out=as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+2)],collapse=''))-+10^(-n+1)}
  return(out)
}

signif.ceiling=function(x,n){
  if(x==0)(out=0)
  if(x%%round(x)==0 & sign(x)==1){out=(as.numeric(paste0(el(strsplit(as.character(x),''))[1:n],collapse=''))+1)*10^(nchar(x)-n)}
  if(x%%round(x) >0 & sign(x)==1){out=as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+1)],collapse=''))+10^(-n+1)}
  if(x%%round(x)==0 & sign(x)==-1){out=(as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+1)],collapse='')))*10^(nchar(x)-n-1)}
  if(x%%round(x) < 0 & sign(x)==-1){out=as.numeric(paste0(el(strsplit(as.character(x),''))[1:(n+2)],collapse=''))}
  return(out)
}
Andrew Haynes
  • 2,612
  • 2
  • 20
  • 35
  • Interesting, but the above solution seems to be working only with integers, not with decimal or negative numbers: `signif.floor(1.234,2)` returns `1000` (expected 1.2) , and `signif.floor(-123,2)` returns `-100` (expected -130). – Heikki Nov 08 '17 at 11:08
  • Updated my answer - not sure it was worth the hassle in the end :) – Andrew Haynes Nov 08 '17 at 11:46
  • I figured out a solution which worked with single values, but @storaged's solution is such that there is no need to use any kind of `apply` functions when using a list or a matrix as an argument for the function. – Heikki Nov 08 '17 at 12:17