3

The signif() function rounds values based on the specificed number of significant digits in a given value. The function applies to all the digits in the number, though, so numbers of different orders of magnitude are rounded and truncated differently. For instance, with this vector x

x <- c(100.1303421, 10.03421, 1.3421, 0.0003421)
lapply(x, signif, 2)
#> [[1]]
#> [1] 100
#> 
#> [[2]]
#> [1] 10
#> 
#> [[3]]
#> [1] 1.3
#> 
#> [[4]]
#> [1] 0.00034

… the first and second values are truncated to 100 and 10 and the decimal values disappear, while the third and fourth values maintain decimals (though different numbers). This is the expected output from signif()—it rightfully takes into account all the digits when determining significant.

Is it possible to ignore the integer part of each number and only format the decimal part with a set number of significant digits? I'd like to be able to return these values: c(100.13, 10.034, 1.34, 0.00034); that is, the complete integer preceding the ., followed by the significant digits of the decimal portion of the number.

For now, I've created a function that separates values into integer and fraction components and runs signif() only on the fraction component, but this seems exceptionally hacky.

signif_ignore_integer <- function(x, digits = 2) {
  as.integer(x) + signif(x - as.integer(x), digits)
}

lapply(x, signif_ignore_integer, 2)
#> [[1]]
#> [1] 100.13
#> 
#> [[2]]
#> [1] 10.034
#> 
#> [[3]]
#> [1] 1.34
#> 
#> [[4]]
#> [1] 0.00034

Is there a better, more recommended way (possibly with some fancy sprintf() format) to round values to a given number of significant digits while ignoring their integer components?

Andrew
  • 36,541
  • 13
  • 67
  • 93
  • 3
    There may be a better way, but your approach doesn't seem unreasonable. But you can streamline it by using `floor` and taking advantage of vectorization to avoid `lapply`: `my_signif = function(x, digits) floor(x) + signif(x - floor(x), digits); my_signif(x,2)` – eipi10 Mar 27 '17 at 15:55
  • Oh excellent—it's reassuring to know that this isn't unreasonable. – Andrew Mar 27 '17 at 16:04
  • I realized there's an even more streamlined approach which I've posted as an answer (and which @Ryan just posted in a (now-deleted) comment as well). – eipi10 Mar 27 '17 at 16:08
  • It appears that the pre-formatted numbers (`sprintf` and `formatC`) don't support what you are trying to do. Take a look at the text in the help file `?formatC` for the digits argument. "f" supports number of digits after the decimal, while others like "g" support display of significant digits. – lmo Mar 27 '17 at 16:08
  • Yeah, I've spent way too much time trying to figure out some `sprintf` incantation that would let me use "g" with only decimal numbers like "f", but it doesn't look possible. – Andrew Mar 27 '17 at 16:14
  • 1
    Here is an sprint method, though it's not pretty: `temp <- sprintf("%.2g", x - trunc(x)); paste0(trunc(x), substr(temp, 2, nchar(temp)))`. – lmo Mar 27 '17 at 16:19

1 Answers1

1

There may be a better way, but your general approach doesn't seem unreasonable. However, you can streamline it by using floor, taking advantage of vectorization to avoid lapply, and using the mod operator to return the fractional part of each number:

my_signif = function(x, digits) floor(x) + signif(x %% 1, digits)

my_signif(x, 2)
[1] 100.13000  10.03400   1.34000   0.00034
eipi10
  • 91,525
  • 24
  • 209
  • 285
  • I'm guessing speed probably isn't an issue here, but for some reason `trunc` is a little faster than `floor`. – Ryan Mar 27 '17 at 16:08
  • It looks like `trunc()` breaks with negative numbers, though: `my_signif(-10.03421, 2)` = -9.03. Replacing it with `floor()` gives the correct result of -10.03 – Andrew Mar 27 '17 at 16:23