23

I want to count the number of digits before the decimal point for a numeric vector x with numbers greater or equal to 1. For example, if the vector is

x <- c(2.85, 356.01, 66.1, 210.0, 1445.11, 13.000)

my code should return a vector containing integers 1, 3, 2, 3, 4, 2

Does any know how to do this?

Jaap
  • 81,064
  • 34
  • 182
  • 193
Cindy
  • 425
  • 1
  • 3
  • 10
  • 3
    Possible duplicate of [calculate the number of digits in a numeric vector in R](https://stackoverflow.com/questions/31894169/calculate-the-number-of-digits-in-a-numeric-vector-in-r) -- only need a slight variation on that: `nchar(sub('\\.[0-9]+', '', x))` – duckmayr Nov 08 '17 at 22:24
  • 1
    Not very versed in R, but one option is to `int(floor(x))`, to drop the trailing decimals in `x`, and then you could convert to a string and count the characters. – Daniel R. Livingston Nov 08 '17 at 22:25
  • `nchar(as.integer(x))` – LyzandeR Nov 08 '17 at 22:27
  • Yeah, very likely a dupe, but I'd do `ceiling(log10(x))`, which doesn't appear in duck's link. (See Gregor's comment for a correction.) – Frank Nov 08 '17 at 22:27
  • 2
    @Frank I think `floor(log10(x)) + 1`, so that 10 powers are counted correctly. – Gregor Thomas Nov 08 '17 at 22:28
  • 1
    Language agnostic version of the q: https://stackoverflow.com/questions/6655754/finding-the-number-of-digits-of-an-integer – Frank Nov 08 '17 at 22:34

2 Answers2

35

This is probably the best way (for positive numbers):

floor(log10(x)) + 1

If you want an answer that works for negative numbers too, add in an abs():

floor(log10(abs(x))) + 1

The log10 method will not work if the input is exactly 0, so if you want a robust solution with that method, handle 0 as a special case:

n_int_digits = function(x) {
  result = floor(log10(abs(x)))
  result[!is.finite(result)] = 0
  result
}

You can also use nchar(trunc(x)), but this seems to behave poorly for large numbers. It will also count leading 0s, whereas the log method will not.

Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • 1
    OP's question was specifically about numbers greater than 1. Numbers less than 1 would need additional handling, something like `ifelse(x < 1, 0, ::code from the answer::)`... depending on how you want negative numbers handled. – Gregor Thomas Aug 07 '18 at 14:36
  • 1
    `nchar(trunc(x))` fails for negative numbers. I know you were discussing the logarithm solution, but I figured I'd point that out too. – Gabriel J. Odom Apr 30 '19 at 14:02
  • 2
    nchar(trunc(x)) fails for huge numbers. For example, ```n<-1.23e+20``` gives ```nchar(trunc(n))``` as ```8```! – J. Mini May 02 '20 at 21:24
15

Character Counting

For small problems, I like the nchar() solution the best, with one modification for negative values:

nDigits <- function(x) nchar( trunc( abs(x) ) )

# Test
nDigits(100)
nDigits(-100)
# both have 3 digits

nDigits(3)
nDigits(-3)
nDigits(0.1)
nDigits(-0.1)
# all have 1 digit

nDigits(1 / .Machine$double.eps)
nDigits(-1 / .Machine$double.eps)
# both have 16 digits

Base 10 Logarithm

If you want to make the logarithm solution work, then you need considerations for negative values and values between 0 and 1. To me, this solution is a tad more complicated:

nDigits2 <- function(x){

  truncX <- floor(abs(x))

  if(truncX != 0){
    floor(log10(truncX)) + 1
  } else {
    1
  }

}

Speed Performance

Here is the output from the microbenchmark comparison (100,000 reps). The code for the character-counting solution is simpler, but slower (by a factor of 3-4):

For integers > 1 (Unit: nanoseconds):

          expr  min   lq      mean median   uq     max neval
  nDigits(100) 1711 2139 2569.2819   2566 2994 2234046 1e+05
 nDigits2(100)    0  428  861.5435    856  856 5670216 1e+05

For really tiny decimals (Unit: nanoseconds):

                           expr  min   lq     mean median   uq     max neval
 nDigits(1/.Machine$double.eps) 2994 4277 5066.321   4705 4705 4477928 1e+05
nDigits2(1/.Machine$double.eps)  428 1283 1588.382   1284 1711 2042458 1e+05
Gabriel J. Odom
  • 336
  • 2
  • 9