5

What's the best way to write

int NumDigits(int n);

in C++ which would return the number of digits in the decimal representation of the input. For example 11->2, 999->3, -1->2 etc etc.

jjerms
  • 1,095
  • 1
  • 10
  • 10

14 Answers14

17

Straightforward and simple, and independent of sizeof(int):

int NumDigits(int n) {
    int digits = 0;
    if (n <= 0) {
        n = -n;
        ++digits;
    }
    while (n) {
        n /= 10;
        ++digits;
    }
    return digits;
}
Thomas
  • 174,939
  • 50
  • 355
  • 478
  • You just need to be wary with the negation here. -INT_MIN may be problematic and, in any case. C/C++ doesn't guarantee 2s complement. You could *technically* have INT_MIN of -32768 and INT_MAX of 2 – paxdiablo Nov 08 '09 at 12:28
  • 1
    paxdiablo, `INT_MAX` is guaranteed to be at least 32767. – avakar Nov 08 '09 at 12:50
  • ...which of course does not really invalidate your comment. Just nitpicking. – avakar Nov 08 '09 at 12:51
  • I took out the `n = -n;` line, which was redundant anyway. Now it should be correct no matter what `INT_MIN` and `INT_MAX` are. – Thomas Nov 08 '09 at 12:59
  • Actually, it wasn't redundant at all. The behavior of `/` is implementation-defined for negative numbers, it is only guaranteed that `(a/b)*b+a%b==a` holds. `-1/2` may easily be `-1` (with `-1%2==1`). Your code may fail to terminate. – avakar Nov 08 '09 at 14:06
  • 1
    Though, in C++0x the behavior is defined to truncate :) See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#614 – Johannes Schaub - litb Nov 08 '09 at 14:57
  • I think you can just add `1` if you see the number is `INT_MIN` and then negate. I wonder though if it's guaranteed that this will not go from a `-...000` to a `-...99`, and thereby miss one digit. – Johannes Schaub - litb Nov 08 '09 at 15:33
  • Oh dear. I didn't know that. Undefined? That's... hideous... I'll roll back to the previous version. – Thomas Nov 08 '09 at 19:21
  • Not undefined, but implementation-defined. Means that all compiler vendors need to state whether they round up or down. Not sure whether this or the previous version is better. There are loads of 2s complement systems around where your code fails for `INT_MIN`, but i don't know a system that actually rounds down for negative division (anyone?) – Johannes Schaub - litb Nov 09 '09 at 02:40
  • Actually, this is something you could test using a template, or using `#if -1 / 2 == 0`, i think. – Johannes Schaub - litb Nov 09 '09 at 02:45
  • this is anything but fast https://youtu.be/vrfYLlR8X8k?list=PLw_k9oJoNaaynnIFM5algic5zoHphCrFg&t=3839 – Gabriel Jun 04 '19 at 17:04
  • 1
    @Gabriel Super interesting, thanks for sharing. I will edit out the "fast" claim because without knowing about hardware, this is entirely unfounded anyway. (I assume that Alexandrescu made some assumptions about hardware earlier in his talk.) – Thomas Jun 05 '19 at 12:07
  • @Thomas I'm glad you watched the thing! I came up with my own improvement on alexandrescu's version. Will post it here. – Gabriel Jun 06 '19 at 17:11
12
//Works for positive integers only
int DecimalLength(int n) {
    return floor(log10f(n) + 1);
}
vava
  • 24,851
  • 11
  • 64
  • 79
  • I'd add 0.9 to n, on the off chance the log10f gets rounded down for some power of 10 (or test all such edge cases if it's to be deployed on one hardware system only). – Pete Kirkham Nov 08 '09 at 11:34
  • 9
    Very iffy. I'd never reach for floating-point arithmetic if integer arithmetic should suffice. – Thomas Nov 08 '09 at 12:06
  • 1
    @Thomas, I don't think loop (even small one) will be faster than logarithm calculation. – vava Nov 08 '09 at 12:28
  • 1
    I don't think Thomas' comment was about speed -- using float-point makes programs harder to reason about. Anyway, there is no `log10f` in C++ (yet, use `log10((float)n)` for now) and `floor` is unnecessary. – avakar Nov 08 '09 at 12:46
  • @avakar: harder to reason about? how? You can make a very simple case differentiation for the above code and handle each of the cases (positive, 0, negative) separately. Throw in an observation of the largest representable numbers for good measure. Nothing hard to reason about. – Konrad Rudolph Nov 08 '09 at 13:31
  • Konrad, even people with reputation like Pete deem it necessary to exercise care when dealing with float-point. So would *you* add 0.9 to `n`? Please show me the simple reasoning behind your answer. – avakar Nov 08 '09 at 13:42
10

The fastest way is probably a binary search...

//assuming n is positive
if (n < 10000)
    if (n < 100)
        if (n < 10)
            return 1;
        else
            return 2;
    else
        if (n < 1000)
            return 3;
        else
            return 4;
 else
     //etc up to 1000000000

In this case it's about 3 comparisons regardless of input, which I suspect is much faster than a division loop or using doubles.

Artelius
  • 48,337
  • 13
  • 89
  • 105
  • That said, if there's some magic optimisation of this, then the division loop is probably the best way to express it for the compiler to be able to apply that optimisation. – Artelius Nov 08 '09 at 11:41
  • Quicker still would be a lookup table--if you don't mind using your whole memory space for that... :) Still, an elegant solution but you'll want a lot of test cases! – Drew Hall Nov 08 '09 at 11:57
  • ...did I say elegant? I meant smart & efficient. It's ugly as sin but would blow the doors off my division loop in practice! :) – Drew Hall Nov 08 '09 at 11:59
8

One way is to (may not be most efficient) convert it to a string and find the length of the string. Like:

int getDigits(int n)
{
    std::ostringstream stream;
    stream<<n;

    return stream.str().length();
}
Naveen
  • 74,600
  • 47
  • 176
  • 233
  • I like this -- it's the simplest thing that you know will work. – Josh Lee Nov 08 '09 at 11:47
  • 3
    Not only is this solution fine unless you need to compute a lot of these in a tight loop, it's the only one that was bug-free on the first try. There's something to be said about that. – Josh Lee Nov 08 '09 at 14:25
7

To extend Arteluis' answer, you could use templates to generate the comparisons:

template<int BASE, int EXP>
struct Power
{
    enum {RESULT = BASE * Power<BASE, EXP - 1>::RESULT};
};

template<int BASE>
struct Power<BASE, 0>
{
    enum {RESULT = 1};
};

template<int LOW = 0, int HIGH = 8>
struct NumDigits
{
    enum {MID = (LOW + HIGH + 1) / 2};

    inline static int calculate (int i)
    {
        if (i < Power<10, MID>::RESULT)
            return NumDigits<LOW, MID - 1>::calculate (i);
        else
            return NumDigits<MID, HIGH>::calculate (i);
    }
};

template<int LOW>
struct NumDigits<LOW, LOW>
{
    inline static int calculate (int i)
    {
        return LOW + 1;
    }
};

int main (int argc, char* argv[])
{
    // Example call.
    std::cout << NumDigits<>::calculate (1234567) << std::endl;

    return 0;
}
jon hanson
  • 8,722
  • 2
  • 37
  • 61
  • 1
    Though I doubt it'll be worth the effort, +1 for a brilliant and borderline useful example of template metaprogramming! – Thomas Nov 08 '09 at 13:06
  • 1
    OMG, my eyes are bleeding :-) – paxdiablo Nov 08 '09 at 13:08
  • It might seem a tad gratuitous, however i do think it brings the algorithm to the fore, unlike the nested comparisons. – jon hanson Nov 08 '09 at 13:34
  • It would be much more useful if you cared to provide the entry point into this maze. In simple words: How do I use it now? I can figure it out, but for a less prepared reader it is simply useless for that reason alone. – AnT stands with Russia Nov 08 '09 at 17:44
6
numdigits = snprintf(NULL, 0, "%d", num);
Dipstick
  • 9,854
  • 2
  • 30
  • 30
3
int NumDigits(int n)
{
  int digits = 0;

  if (n < 0) {
    ++digits;
    do {
      ++digits;
      n /= 10;
    } while (n < 0);
  }
  else {
    do {
      ++digits;
      n /= 10;
    } while (n > 0);
  }

  return digits;
}

Edit: Corrected edge case behavior for -2^31 (etc.)

Drew Hall
  • 28,429
  • 12
  • 61
  • 81
3

Some very over-complicated solutions have been proposed, including the accepted one.

Consider:

#include <cmath>
#include <cstdlib>

int NumDigits( int num )
{
    int digits = (int)log10( (double)abs(num) ) + 1 ;

    return num >= 0 ? digits : digits + 1 ;
}

Note that it works for for INT_MIN + 1 ... INT_MAX, because abs(INT_MIN) == INT_MAX + 1 == INT_MIN (due to wrap-around), which in-turn is invalid input to log10(). It is possible to add code for that one case.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • This goes wrong on large numbers, where the `double` representation can no longer represent the `int` exactly (e.g. on platforms where both `int` and `double` are 64 bits). – Thomas Jun 07 '19 at 06:18
  • @Thomas Good point. I had assumed 32bit int. 10 years later I am not sure I'd make the same assumption, or would at least point out the limitation. – Clifford Jun 07 '19 at 06:40
2

Here's a simpler version of Alink's answer .

int NumDigits(int32_t n)
{
    if (n < 0) {
        if (n == std::numeric_limits<int32_t>::min())
            return 11;
        return NumDigits(-n) + 1;
    }

    static int32_t MaxTable[9] = { 10,100,1000,10000,100000,1000000,10000000,100000000,1000000000 };
    return 1 + (std::upper_bound(MaxTable, MaxTable+9, n) - MaxTable);
}
xamid
  • 440
  • 11
  • 25
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Indeed, it simplifies nicely small usual case like int. However, typing all these zeros seems error prone. I suggest using multiline to better show(and verify) the incrementation. – Alink Nov 14 '09 at 14:08
  • This is incorrect for INT_MIN, there has to be made a special case for it since -INT_MIN will overflow. – xamid Mar 09 '21 at 18:34
  • @xamid that's true, but it's easily fixed with a single `if` statement. Not sure what to return in that case though, it depends on the size of your `int` type. – Mark Ransom Mar 09 '21 at 18:58
  • @MarkRansom The above code works only correct for int having at most 32 bits. I used `int32_t` instead of `int` for clarification, and `if (n == std::numeric_limits::min()) return 11;` for the special case. – xamid Mar 09 '21 at 19:08
  • @xamid usually I try to minimize the changes to the code given in the question, but since you made a good point again I made an exception. – Mark Ransom Mar 09 '21 at 19:49
1

Another implementation using STL binary search on a lookup table, which seems not bad (not too long and still faster than division methods). It also seem easy and efficient to adapt for type much bigger than int: will be faster than O(digits) methods and just needs multiplication (no division or log function for this hypothetical type). There is a requirement of a MAXVALUE, though. Unless you fill the table dynamically.

[edit: move the struct into the function]

int NumDigits9(int n) {
    struct power10{
        vector<int> data;
        power10() { 
            for(int i=10; i < MAX_INT/10; i *= 10) data.push_back(i);
        }
    };

    static const power10 p10;
    return 1 + upper_bound(p10.data.begin(), p10.data.end(), n) - p10.data.begin();
}
Alink
  • 394
  • 2
  • 4
1

Since the goal is to be fast, this is a improvement on andrei alexandrescu's improvement. His version was already faster than the naive way (dividing by 10 at every digit). The version below is faster at least on x86-64 and ARM for most sizes.

Benchmarks for this version vs alexandrescu's version on my PR on facebook folly.

inline uint32_t digits10(uint64_t v)
{
  std::uint32_t result = 0;
  for (;;)
  {
    result += 1
            + (std::uint32_t)(v>=10)
            + (std::uint32_t)(v>=100)
            + (std::uint32_t)(v>=1000)
            + (std::uint32_t)(v>=10000)
            + (std::uint32_t)(v>=100000);
    if (v < 1000000) return result;
    v /= 1000000U;
  }
}
Gabriel
  • 2,841
  • 4
  • 33
  • 43
0

My version of loop (works with 0, negative and positive values):

int numDigits(int n)
{
   int digits = n<0;  //count "minus"
   do { digits++; } while (n/=10);
   return digits;
}
P Shved
  • 96,026
  • 17
  • 121
  • 165
  • Counts the digits of negative numbers, but doesn't count the minus. The question (now) states that it should include the minus sign too. – Thomas Nov 08 '09 at 13:30
  • Very nice. The most elegant yet. I'd upvote, but it suffers from the same non-termination problem on unusual platforms as Thomas' answer. – avakar Nov 08 '09 at 14:10
0

If you're using a version of C++ which include C99 maths functions (C++0x and some earlier compilers)

static const double log10_2 = 3.32192809;

int count_digits ( int n )
{
    if ( n == 0 ) return 1;
    if ( n < 0 ) return ilogb ( -(double)n ) / log10_2 + 2;
    return ilogb ( n ) / log10_2 + 1;
}

Whether ilogb is faster than a loop will depend on the architecture, but it's useful enough for this kind of problem to have been added to the standard.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
0

An optimization of the previous division methods. (BTW they all test if n!=0, but most of the time n>=10 seems enough and spare one division which was more expensive).

I simply use multiplication and it seems to make it much faster (almost 4x here), at least on the 1..100000000 range. I am a bit surprised by such difference, so maybe this triggered some special compiler optimization or I missed something.

The initial change was simple, but unfortunately I needed to take care of a new overflow problem. It makes it less nice, but on my test case, the 10^6 trick more than compensates the cost of the added check. Obviously it depends on input distribution and you can also tweak this 10^6 value.

PS: Of course, this kind of optimization is just for fun :)

int NumDigits(int n) {
    int digits = 1;
    // reduce n to avoid overflow at the s*=10 step.
    // n/=10 was enough but we reuse this to optimize big numbers
    if (n >= 1000000) {
        n /= 1000000;
        digits += 6; // because 1000000 = 10^6
    }
    int s = 10;
    while (s <= n) {
        s *= 10;
        ++digits;
    }
    return digits;
}
Alink
  • 394
  • 2
  • 4