tags:

views:

1348

answers:

9

for instance,

n = 3432, result 4

n = 45, result 2

n = 33215, result 5

n = -357, result 3

I guess I could just turn it into a string then get the length of the string but that seems convoluted and hack-y.

+4  A: 

Divide by 10 in a loop until the result reaches zero. The number of iterations will correspond to the number of decimal digits.

Assuming that you expect to get 0 digits in a zero value:

int countDigits( int value )
{
    int result = 0;
    while( value != 0 ) {
       value /= 10;
       result++;
    }
    return result;
}
sharptooth
floor(log10(abs(x)))+1 would be faster, but eduffy has already suggested that! :-)
Workshop Alex
I'd be curious to see that timed. I'd almost think that an optimized series of if statements (based on maxint) may outperform a floating point logarithm (but I'm too lazy to test it myself).
paxdiablo
It's never going to reach zero, is it?
John Pirie
@John Pirie: Why wouldn't it? I mean integer division and when applied iteratively to the same variable it will eventually give zero.
sharptooth
@JP, if you keep dividing an integer by 10, it will reach zero eventually.
paxdiablo
@sharp/Pax, doh, was thinking in doubles, you're absolutely right, thanks.
John Pirie
Actually, @Workshop, the floating point solution *isn't* faster but on par with this iterative solution. It's slower than the hand-optimized if-statement solution by a factor of about seven.
paxdiablo
This is SLOW, because you use divisions, which are slow (~7-10 times slower than multiplications, latency-wise). Instead of dividing the value, multiply the threshold you compare it to.
stormsoul
@me:Alright, these are divisions by a small constant, so they're only ~2-5 times slower (on a good optimizing compiler). Still, the main point I made holds :).
stormsoul
A good optimizing compiler replaces the division by constant with a multiplication and a shift and this stops being so slow.
sharptooth
stormsoul
+25  A: 
floor (log10 (abs (x))) + 1

http://en.wikipedia.org/wiki/Logarithm

eduffy
Not when x is zero.
finnw
so check that it isn't zero before...
Nathan Fellman
I'd say ceil(...) instead of floor(...)+1. And of course a check for zero, as said.
Lennaert
@Len: `ceil` won't work for powers of 10: ceil(log10 (100)) == 2
eduffy
@eduffy: Good point, didn't think of that.
Lennaert
My ancient understanding of C-type math libraries was that log and similar functions were fairly expensive, so I would tend to favor simple division, a la Pax's solution. Anyone know/comment?
John Pirie
@John, it is substantially slower but it doesn't really make a difference until you get into the hundred million iteration range (where the difference is seven seconds for floating point and 1 second for worst case optimized if statements). See my update.
paxdiablo
This would be needlessly slow. Don't use expensive functions such as log10() without a reason. The fast, integer function is simple enough to bother writing it.
stormsoul
Also, it would fail on x==0 AND x==INT_MIN, as usually abs(INT_MIN)==INT_MIN which is negative :). Cast the result of abs() to unsigned int to make the code slightly slower and more correct :).
stormsoul
Geez .. are you people still running an 8088? Who cares about few extra clock cycles. It took Paz 100,000,000 iterations to make a measurable difference, and even that was negligible! 6 seconds! Whoop-dee-do .. get on with your life. Next year it'll be 3 seconds.
eduffy
@eduffy:A millisecond here, a millisecond there... and suddenly, the user feels a noticable delay after clicking a button. Seriously, those small inefficiencies add up. Why waste clock cycles, when you don't gain anything by it?
stormsoul
@eduffy: if this were running on an embedded processor, there might not be any floating point support, let alone log functions, and the clock speed may only be in the tens of MHz - so an entirely integer-based solution would definitely be the preferred option.
Steve Melnikoff
In any case, we're mostly geeks here; surely faster is better, no matter what? ;-)
Steve Melnikoff
It turns out that although simple division is faster for small values, logarithm scales much better. If you call the division algorithms with every int from MIN_INT to MAX_INT (and repeat that the same 100m times as Paz's examples), you end up with an average of 13.337 seconds per call.Doing the same with Logarithm is an average of 8.143 seconds, the recursion takes 11.971 seconds, and the cascading If statements ends up taking an average of 0.953 seconds.So, the Daily-WTF-looking solution is an order of magnitude faster, but in the long run, this is in second place.
Matt Poush
Why not go with this if you think this is the clearest way (which in my opinion is), and if you really find it actually makes a difference you can easily switch to any of the other solutions, which are all small and nifty.
Skurmedel
@Matt Poush:What? Either you don't understand something, or have a really braindead compiler. You repeat the division at most n times, where n is the number of digits in the number (10 with 32-bit int). And the division is actually implemented as a multiplication by a good compiler. That's WAY faster than doing a single FP logarithm.By the way, ALL compilers are braindead if you don't turn optimization on :).
stormsoul
@stormsoul, are we still doing intensive calculations on a single thread? That was so last year.
Pyrolistical
+41  A: 

The recursive approach :-)

int numPlaces (int n) {
    if (n < 0) return numPlaces ((n == MININT) ? MAXINT : -n);
    if (n < 10) return 1;
    return 1 + numPlaces (n / 10);
}

Or iterative:

int numPlaces (int n) {
    int r = 1;
    if (n < 0) n = (n == MININT) ? MAXINT : -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

Or raw speed:

int numPlaces (int n) {
    if (n < 0) n = (n == MININT) ? MAXINT : -n;
    if (n < 10) return 1;
    if (n < 100) return 2;
    if (n < 1000) return 3;
    if (n < 10000) return 4;
    if (n < 100000) return 5;
    if (n < 1000000) return 6;
    if (n < 10000000) return 7;
    if (n < 100000000) return 8;
    if (n < 1000000000) return 9;
    /*      2147483647 is 2^31-1 - add more ifs as needed
       and adjust this final return as well. */
    return 10;
}

Those above have been modified to better process MININT. On any weird systems that don't follow sensible 2n two's complement rules for integers, they may need further adjustment.

The raw speed version actually outperforms the floating point version, modified below:

int numPlaces (int n) {
    if (n == 0) return 1;
    return floor (log10 (abs (n))) + 1;
}

With a hundred million iterations, I get the following results:

Raw speed with 0:            0 seconds
Raw speed with 2^31-1:       1 second
Iterative with 2^31-1:       5 seconds
Recursive with 2^31-1:       6 seconds
Floating point with 1:       6 seconds
Floating point with 2^31-1:  7 seconds

That actually surprised me a little - I thought the Intel chips had a decent FPU but I guess general FP operations still can't compete with hand-optimized integer code.

Update following stormsoul's suggestions:

Testing the multiply-iterative solution by stormsoul gives a result of 4 seconds so, while it's much faster than the divide-iterative solution, it still doesn't match the optimized if-statement solution.

Choosing the arguments from a pool of 1000 randomly generated numbers pushed the raw speed time out to 2 seconds so, while it appears there may have been some advantage to having the same argument each time, it's still the fastest approach listed.

Compiling with -O2 improved the speeds but not the relative positions (I increased the iteration count by a factor of ten to check this).

Any further analysis is going to have to get seriously into the inner workings of CPU efficiency (different types of optimization, use of caches, branch prediction, which CPU you actually have, the ambient temperature in the room and so on) which is going to get in the way of my paid work :-). It's been an interesting diversion but, at some point, the return on investment for optimization becomes too small to matter. I think we've got enough solutions to have answered the question (which was, after all, not about speed).

Further update:

This will be my final update to this answer barring glaring errors that aren't dependent on architecture. Inspired by stormsoul's valiant efforts to measure, I'm posting my test program (modified as per stormsoul's own test program) along with some sample figures for all methods shown in the answers here. Keep in mind this is on a particular machine, your mileage may vary depending on where you run it (which is why I'm posting the test code).

Do with it as you wish:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>

#define numof(a) (sizeof(a) / sizeof(a[0]))

/* Random numbers and accuracy checks. */

static int rndnum[10000];
static int rt[numof(rndnum)];

/* All digit counting functions here. */

static int count_recur (int n) {
    if (n < 0) return count_recur ((n == INT_MIN) ? INT_MAX : -n);
    if (n < 10) return 1;
    return 1 + count_recur (n / 10);
}

static int count_diviter (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

static int count_multiter (int n) {
    unsigned int num = abs(n);
    unsigned int x, i;
    for (x=10, i=1; ; x*=10, i++) {
        if (num < x)
            return i;
        if (x > INT_MAX/10)
            return i+1;
    }
}

static int count_ifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n < 10) return 1;
    if (n < 100) return 2;
    if (n < 1000) return 3;
    if (n < 10000) return 4;
    if (n < 100000) return 5;
    if (n < 1000000) return 6;
    if (n < 10000000) return 7;
    if (n < 100000000) return 8;
    if (n < 1000000000) return 9;
    /*      2147483647 is 2^31-1 - add more ifs as needed
    and adjust this final return as well. */
    return 10;
}

static int count_revifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n > 999999999) return 10;
    if (n > 99999999) return 9;
    if (n > 9999999) return 8;
    if (n > 999999) return 7;
    if (n > 99999) return 6;
    if (n > 9999) return 5;
    if (n > 999) return 4;
    if (n > 99) return 3;
    if (n > 9) return 2;
    return 1;
}

static int count_log10 (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n == 0) return 1;
    return floor (log10 (n)) + 1;
}

static int count_bchop (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n >= 100000000) {
        r += 8;
        n /= 100000000;
    }
    if (n >= 10000) {
        r += 4;
        n /= 10000;
    }
    if (n >= 100) {
        r += 2;
        n /= 100;
    }
    if (n >= 10)
        r++;

    return r;
}

/* Structure to control calling of functions. */

typedef struct {
    int (*fnptr)(int);
    char *desc;
} tFn;

static tFn fn[] = {
    NULL,                              NULL,
    count_recur,    "            recursive",
    count_diviter,  "     divide-iterative",
    count_multiter, "   multiply-iterative",
    count_ifs,      "        if-statements",
    count_revifs,   "reverse-if-statements",
    count_log10,    "               log-10",
    count_bchop,    "          binary chop",
};
static clock_t clk[numof (fn)];

int main (int c, char *v[]) {
    int i, j, k, r;
    int s = 1;

    /* Test code:
        printf ("%11d %d\n", INT_MIN, count_recur(INT_MIN));
        for (i = -1000000000; i != 0; i /= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 0, count_recur(0));
        for (i = 1; i != 1000000000; i *= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 1000000000, count_recur(1000000000));
        printf ("%11d %d\n", INT_MAX, count_recur(INT_MAX));
    /* */

    /* Randomize and create random pool of numbers. */

    srand (time (NULL));
    for (j = 0; j < numof (rndnum); j++) {
        rndnum[j] = s * rand();
        s = -s;
    }
    rndnum[0] = INT_MAX;
    rndnum[1] = INT_MIN;

    /* For testing. */
    for (k = 0; k < numof (rndnum); k++) {
        rt[k] = (fn[1].fnptr)(rndnum[k]);
    }

    /* Test each of the functions in turn. */

    clk[0] = clock();
    for (i = 1; i < numof (fn); i++) {
        for (j = 0; j < 10000; j++) {
            for (k = 0; k < numof (rndnum); k++) {
                r = (fn[i].fnptr)(rndnum[k]);
                /* Test code:
                    if (r != rt[k]) {
                        printf ("Mismatch error [%s] %d %d %d %d\n",
                            fn[i].desc, k, rndnum[k], rt[k], r);
                        return 1;
                    }
                /* */
            }
        }
        clk[i] = clock();
    }

    /* Print out results. */

    for (i = 1; i < numof (fn); i++) {
        printf ("Time for %s: %10d\n", fn[i].desc, (int)(clk[i] - clk[i-1]));
    }

    return 0;
}

And here's the leaderboard for my environment:

Optimization level 0:

Time for reverse-if-statements:       1704
Time for         if-statements:       2296
Time for           binary chop:       2515
Time for    multiply-iterative:       5141
Time for      divide-iterative:       7375
Time for             recursive:      10469
Time for                log-10:      26953

Optimization level 3:

Time for         if-statements:       1047
Time for           binary chop:       1156
Time for reverse-if-statements:       1500
Time for    multiply-iterative:       2937
Time for      divide-iterative:       5391
Time for             recursive:       8875
Time for                log-10:      25438
paxdiablo
The second craziest use of recursion I know. The first is to find a number of elements in a list.
sharptooth
Hence the smiley :-) I'm actually disappointed that I'm not number one. I'll have to try harder next time.
paxdiablo
True, why do things the easy way when there are such beautiful complex solutions. ;-)
Workshop Alex
Recursive version seems to me like the cleanest, simplest, best self-documenting solution posted.
John Pirie
@sharptooth: But recursive traversal of lists is standard practice in functional languages!
Brian
You can't assume you can get the abs of n if n is negative; if n is the minimum allowed value, you can't negate it. The opposite of -(2^31) is 2^31, which can't be represented as an int.
Matt Poush
@Brian:But C is not a functional language!
stormsoul
@Matt Poush:Yes, by the ISO C standard, the result of abs(MIN_INT) is undefined. But in reality, nearly all machines will return MIN_INT, which is the correct result, if you cast it to unsigned int.
stormsoul
@stormsoul: I wasn't trying to be that pedantic; I was assuming abs(MIN_INT) would return MIN_INT. The problem is that a number of the above functions assume that calling abs(MIN_INT) will return a positive number. For example, the iterative example negates a negative number, and then loops while the value is greater than 10. So, MIN_INT should return 10, but it returns 1, since it falls right through the while conditional.A better solution would be to create your own internal abs method that returns MAX_INT if the value is MIN_INT, or the actual abs value if the value isn't.
Matt Poush
What compiler did you use? Did you turn optimization on? I find it very hard to believe that a well-written iterative approach would be 5x slower than the unrolled version. Not with a good compiler.Also, a better measurement granularity would be much appreciated :)
stormsoul
@Matt Poush:Matt, it is sufficient to cast the result of abs() to unsigned int. The cast will flip MIN_INT to MAX_INT+1.
stormsoul
I have now read the code you tested. It is no wonder the iterative version you gave is slow, because it uses divisions, which are slow. Could you please time the multiplication-based iterative approach I have given?
stormsoul
gcc on Ubuntu 9.04, no particular flags so I don't know what the default optimization was (but it was in one executable anyway so they had the same optimization). The grnularity was 1 second (using time(0)) and averaged over 10 runs. The improvement was, I suspect, due to the fact that the raw speed version is doing only comparisons, not divisions. Your multiplication one may well do better (that was a nifty way around the speed issue but I can't test it now since the machine's in the bedroom and the wife's gone off to sleep). I'll give it a shot in the morning if you wish.
paxdiablo
Re the MININT problem, you could just add detection for that up front: (1) if they're power-of-two based, use "n = (n == MININT) ? MAXINT : -n". There's no power-of-two within one of a power-of ten so that would still return the right number of digits. (2) If they're wildly different (MAXINT = 100, MININT = -7), multiplex to two different raw-speed functions, one for +ve, the other for -ve. No doubt there are other solutions as well, they're just the ones I thought of off the top of my head. That particular problem affects all of the n=-n and n=abs(n) solutions.
paxdiablo
Sorry for comment-spamming, but I just got another idea ;).If you repeatedly time your function on THE SAME ARGUMENT, then no wonder your unrolled version will beat the asses out of every other. This is because the CPU will remember which branches are taken, and which are not, and rush through the code WITHOUT WAITING for the checks. Make an array of ~10000 random values (not more, to keep it in the L1 cache) and time the functions on that.
stormsoul
@Pax:You do not need to test for MININT - just cast it to unsigned int, which will give you the right result for NO cost. The (newer revisions of) ISO C standard guarantees integers are power-of-two based with a representation of signed integers being one of 3 possibilities allowed by the standard. The only one of those 3 possibilities where MININT != -MAXINT would be the most common one: the two's complement, where MININT == -(MAXINT+1). So the cast to unsigned int is pretty much a sure way to get the correct result everywhere.
stormsoul
I don't think you can cast blindly (Pax hesitates...) - in two's complement, -1 becomes 2^32-1 which is 10 digits long, not one as required.
paxdiablo
@Pax:GCC by default gives you no optimization. Pass -O2 or even -O3 to turn it on. If you additionally pass -funroll-loops you may even get the loop unrolled automatically by the compiler :).Also, optimization gives VERY different gains on different code, so it would change the ranking considerably. Your version and the float version would probably not gain much - if anything. The loops - quite on the contrary!
stormsoul
@Pax:Just HOW IN THE WORLD would you like abs() to return -1? The only negative value it can return is MIN_INT :).
stormsoul
@storm, I'm not talking about abs() returning -1, I'm stating that "int i = -1; unsinged int j = (unsigned int)i;" will set j to a big honkin' positive number. Or did you mean something else by your cast without cost?
paxdiablo
Never mind I think I got it it, you mean (unsigned int)(abs(i))
paxdiablo
Why does feeding INT_MIN into your recursive version remind me of the website we are on?
stormsoul
@stormsoul, see comment beginning "Re the MININT problem" above - we've already covered that.
paxdiablo
@Pax:You didn't get the joke :^)
stormsoul
this is too premature optimization. it assumes that the user WOULD need to run this operation a hundred million times. if i'm going to run it only a few dozen times, the time spent optimizing this would be better spent elsewhere.
moogs
@moogs, you can use any of the solutions presented in this, or any other, answer here. The speed testing was really just an aside (that got out of hand). And in any case, you still have that time available to you - it's only *my* time that was possibly wasted here so feel free to use the fruits of my labor as you wish :-)
paxdiablo
You DO realize that rand() never returns negative values, do you? :)
stormsoul
A small performance tip - when you know some value is always non-negative, use unsigned types. They are slightly faster to multiply and divide. The compiler might guess for you that some variable is never negative and make this optimization automatically, but again, it might not. In more complex situations it never does.
stormsoul
Right. Someone in IRC made some performance tests, then he used unsigned and he god some really great boost. I urge you to try the unsigned world! :)
Johannes Schaub - litb
+12  A: 

Binary search pseudo algorithm to get no of digits of r in v..

if (v < 0 ) v=-v;

r=1;

if (v >= 100000000)
{
  r+=8;
  v/=100000000;
}

if (v >= 10000) {
    r+=4;
    v/=10000;
}

if (v >= 100) {
    r+=2;
    v/=100;
}

if( v>=10)
{
    r+=1;
}

return r;
lakshmanaraj
Why the downvote, this looks even more efficient than the divide-by-ten loops?
paxdiablo
Downvote was not from me, but I suspect it was because this is less readable than Wayne Shephard's variation (and probably slower).
Brian
I see, but I don't think it's right to downvote something for being *less* helpful - the popup clearly states "This answer is not helpful". In that case, I would upvote the other and leave this one alone. This was a genuine improvement over the /10 iteration. Still, it's positive now so no harm, no foul. (This isn't directed at you Brian since, as you already said, you didn't do it). Just food for thought for whoever did.
paxdiablo
My algorithm can be easily extended for longlong variable by having another if statement at the beginningif (v >= 10000000000000000LL){ r+=16; v/=10000000000000000LL;}and will be faster than all the approaches.
lakshmanaraj
Why there was a downvote today also?
lakshmanaraj
i voted you up, bro
Johannes Schaub - litb
laksh, I suspect people don't read the code nor the comments.
Skurmedel
I voted this up, having used similar code in base 4 before ...
A. Rex
+2  A: 

You can do: floor (log10 (abs (x))) + 1 Or if you want to save on cycles you could just do comparisons

if(x<10)
  return 1;
if(x<100)
  return 2;
if(x<1000)
  return 3;
etc etc

This avoids any computationally expensive functions such as log or even multiplication or division. While it is inelegant this can be hidden by encapsulating it into a function. It isn't complex or difficult to maintain so I would not dismiss this approach on account of poor coding practice; I feel to do so would be throwing the baby out with the bath water.

Howard May
or I could just throw up a dialog box and ask the user, heh
willc2
That's unlikely to be faster, @willc2, especially with the users I have to deal with on a daily basis.
paxdiablo
And why the downvote here? This turns out to be blindingly fast.
paxdiablo
large, chained if-blocks are code smell, and bad design.
abelenky
Says you! :-)
paxdiablo
the log function would have to be pretty bad if this solution is faster for the general case
David Cournapeau
+1  A: 

int n = 437788; int N = 1; while (n /= 10) N++;

xcramps
What about negative numbers?
ChrisF
Will work okay for negative numbers too - will divide in a loop until n becomes zero and then the loop will stop.
sharptooth
negate it beforehand then, duh.
artificialidiot
No need for negation here. It will iterate until the result equals zero.
sharptooth
@sharptooth - but the "end of loop" test is 10 not 0.
ChrisF
@ChrisF:The end-of-loop test expression in this code is an ASSIGNMENT operator, not a comparison! read it as: while(n = n/10, n!=0) - the last expression after a comma being the real end-of-loop test.
stormsoul
+2  A: 
if (x == MININT) return 10;  //  abs(MININT) is not defined
x = abs (x);
if (x<10) return 1;
if (x<100) return 2;
if (x<1000) return 3;
if (x<10000) return 4;
if (x<100000) return 5;
if (x<1000000) return 6;
if (x<10000000) return 7;
if (x<100000000) return 8;
if (x<1000000000) return 9;
return 10; //max len for 32-bit integers

Very inelegant. But quicker than all the other solutions. Integer Division and FP logs are expensive to do. If performance isn't an issue, the log10 solution is my favorite.

Wayne Sheppard
That actually turns out to be the fastest method even for the worst case (2^32-1) - see my update fo timings.
paxdiablo
But it is total code-smell, and not scalable or portable.
abelenky
I often suspect that "code smell" is a term trotted out by people who just don't like the code - it seems a very unscientific term. This code is perfectly readable (to me at least and to anyone else with half a brain if you add one simple comment line) and will outperform any other solution listed here (very important in the environment I was forged in). And the algorithm is scalable at O(log n) and portable if you just add more if statements to suit the environment you're working in.
paxdiablo
The question is tagged C and math. Any solution is welcome, even the fastest.
Ben
@Pax:Actually, making it a loop should not make it significantly slower (repeatedly multiply the threshold by 10), and will make it more compact. As a bonus, it would be perfectly portable to any possible sizeof(int) when you limit it by MAX_INT or such.
stormsoul
It's fast because it is just one compare per digit. The Iterative solutions are one compare, one division, and one increment per digit. Integer division is expensive, 17 cycles on a C2D. log10 is well over 100 cycles.
Wayne Sheppard
@Wayne Sheppard:You do not need to do divisions with an iterative solution - look at my answer. What's more, the multiplication (which the compiler would transform to 2 shifts and 1 add - 2 cycles total) can be done IN PARALLEL with the increment and compare - assuming the branch after the compare is correctly predicted. This gives 2 cycles / iteration.
stormsoul
@stormsoul, your loop never exits, right? Your test condition is:x<=INT_MAX/10*10 (or x <=2,147,483,640 )After x = 1,000,000,000, x *= 10 overflows, wrapping back to negative. Comparing any integer against MAX_INT is kinda pointless.That said, your multiplication iteration works faster than using division. You just need to figure out how to terminate the loop correctly.
Wayne Sheppard
@Wayne Sheppard:Foo. You're right. Stupid me. I forgot I'm multiplying by 10, and somehow was thinking that the additional bit of precision given by unsigned would be enough - daydreaming, or what? :). Corrected. Thx!BTW, the loop exits, though with a wrong result :). After overflow x would go up until it ended within the (INT_MAX/10*10, UINT_MAX] interval. It would eventually get there, after at most few overflows :).
stormsoul
+3  A: 

From Bit Twiddling Hacks :

Find integer log base 10 of an integer the obvious way

Note the ordering of comparisons in it.

fa.
+1  A: 

DON'T use floor(log10(...)). These are floating-point functions, and slow ones, to add. I believe the fastest way would be this function:

int ilog10(int num)
{
   unsigned int num = abs(num);
   unsigned int x, i;
   for(x=10, i=1; ; x*=10, i++)
   {
      if(num < x)
         return i;
      if(x > INT_MAX/10)
         return i+1;
   }
}

Note that the binary search version some people suggested could be slower due to branch mispredictions.

EDIT:

I did some testing, and got some really interesting results. I timed my function together with all the functions tested by Pax, AND the binary search function given by lakshmanaraj. The testing is done by the following code snippet:

start = clock();
for(int i=0; i<10000; i++)
   for(int j=0; j<10000; j++)
      tested_func(numbers[j]);
end = clock();
tested_func_times[pass] = end-start;

Where the numbers[] array contains randomly generated numbers over the entire range of the int type (barring MIN_INT). The testing was repeated for each tested function on THE SAME numbers[] array. The entire test was made 10 times, with results averaged over all passes. The code was compiled with GCC 4.3.2 with -O3 optimization level.

Here are the results:

floating-point log10:     10340ms
recursive divide:         3391ms
iterative divide:         2289ms
iterative multiplication: 1071ms
unrolled tests:           859ms
binary search:            539ms

I must say I got really astonished. The binary search performed far better than I thought it would. I checked out how GCC compiled this code to asm. O_O. Now THIS is impressive. It got optimized much better than I thought possible, avoiding most branches in really clever ways. No wonder it is FAST.

stormsoul
Well, the fastest way turns out to be unrolling that loop into hand-optimized if statements. But you're dead right about the slowness of floating point.
paxdiablo
@Pax:Floating point being slower than integer is one thing, and log10() and floor() being VERY slow is another. I was referring to the latter.
stormsoul
@Pax:Test this code and we'll see ;).
stormsoul
I'm going to vote this one up for the clever use of multiplication on the threshold rather than division on the value. I suppose you *could* invent a CPU where division was faster but I don't think I've ever seen one, and I'd probably fire the engineer that did it :-)
paxdiablo
Leave it with me, @stormsoul, I'll get back to you in about 8 hours (it's midnight here in Oz).
paxdiablo
@Pax:You couldn't. The division operation is inherently much more complex (and much more *sequential*) than multiplication - making any implementation much slower than what is possible with mults.Incidentally, an optimizing compiler would not emit any divisions for the "division" code! It would transform it into a multiplication by reciprocal. Because the divisor is a small constant, the reciprocal can be computed at compile-time. It would not emit any multiplications for the "multiplication" code, either. This would be transformed into two shifts and one add - for a total of 2 clock cycles.
stormsoul
This multiply-iterative solution bested the divide-iterative one by a factor of two - that's pretty impressive.
paxdiablo