tags:

views:

472

answers:

13

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.

+12  A: 
//Works for positive integers only
int DecimalLength(int n) {
    return floor(log10f(n) + 1);
}
vava
Not hard to tweak this to handle negative, 0 properly...
Drew Hall
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
Very iffy. I'd never reach for floating-point arithmetic if integer arithmetic should suffice.
Thomas
@Thomas, I don't think loop (even small one) will be faster than logarithm calculation.
vava
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
@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
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
+8  A: 

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
I like this -- it's the simplest thing that you know will work.
jleedev
@jleedev: ...slowly
Drew Hall
That reminds me of `if (boolVariable.toString().length() == 4)`...
Pavel Shved
@pavel: I never claimed that its fast..
Naveen
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.
jleedev
+3  A: 
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
This erroneously returns 2 for -2147483648
jleedev
@jleedev: Damn edge cases. Get a machine with 64 bit ints and all is well... :)
Drew Hall
`int` is typically 32 bits still.
jleedev
That's why I said 64 bit "ints" not "integers". Anyway, I fixed the code...
Drew Hall
That's good, because as long as we have twos-complement, that bug can still pop up places :)
jleedev
+1, simply because I like do...whiles
Artelius
@Artelius: do...while: When it absolutely, positively, has to run at least once... :)
Drew Hall
+10  A: 

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
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
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
Drew Hall
+1 for speed, my "best" of choice today :-)
paxdiablo
"Best" choice? Maybe that explains why I got 52 rep for this question...
Artelius
+17  A: 

Clean and fast, 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
A little cleaner than mine. +1!
Drew Hall
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
paxdiablo, `INT_MAX` is guaranteed to be at least 32767.
avakar
...which of course does not really invalidate your comment. Just nitpicking.
avakar
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
+1 for compact, clean code.
paxdiablo
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
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
litb, thanks, I didn't know that :)
avakar
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
Oh dear. I didn't know that. Undefined? That's... hideous... I'll roll back to the previous version.
Thomas
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
Actually, this is something you could test using a template, or using `#if -1 / 2 == 0`, i think.
Johannes Schaub - litb
+6  A: 
numdigits = snprintf(NULL, 0, "%d", num);
Dipstick
Aren't you forgetting something...
Drew Hall
Ta - phone rang and I lost track of where I was.
Dipstick
+7  A: 

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
Though I doubt it'll be worth the effort, +1 for a brilliant and borderline useful example of template metaprogramming!
Thomas
OMG, my eyes are bleeding :-)
paxdiablo
It might seem a tad gratuitous, however i do think it brings the algorithm to the fore, unlike the nested comparisons.
jon hanson
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.
AndreyT
@AndreyT: Fair enough. Done.
jon hanson
A: 

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;
}
Pavel Shved
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
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
A: 

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
This crashes on -2147483648
jleedev
+3  A: 

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
A: 

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
+1  A: 

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
A: 

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

int NumDigits(int n)
{
    if (n < 0)
        return NumDigits(-n) + 1;

    static int MaxTable[9] = { 10,100,1000,10000,100000,1000000,10000000,100000000,1000000000 };
    return 1 + (std::upper_bound(MaxTable, MaxTable+9, n) - MaxTable);
}
Mark Ransom
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