24

There are more than one solution for finding the-number-of-digits in a given number.

For example:

Method-1:

int findn(int num)
{
    char snum[100];
    sprintf(snum, "%d", num);
    return strlen(snum);
}

Method-2:

int findn(int num)
{
    if (num == 0) return 1;
    int n = 0;
    while(num) {
        num /= 10;
        n++;
    }
    return n;
}

Method-3:

int findn(int num)
{
    /* math.h included */
    return (int) log10(num) + 1;
}

The question is - what is the most efficient method? I know method-2 is O(n) but what about method-1 and method-3? How do I find the run-time complexity of library functions?

Kubuntuer82
  • 1,487
  • 3
  • 18
  • 40
Sangeeth Saravanaraj
  • 16,027
  • 21
  • 69
  • 98
  • 3
    It depends what you mean by `O`. `O` usually refers to asymptotic complexity, but `log10`, for example, can only deal with a fixed input range, so it's actually `O(1)`. – Oliver Charlesworth Mar 15 '12 at 13:51
  • 1
    you can always test to find out, and post your results... – AShelly Mar 15 '12 at 13:56
  • Echoing AShelly, the only way to know for sure is to code up all three versions and compare their performance. – John Bode Mar 15 '12 at 14:53
  • If you expect `num=0` to return `1` Method-1 is the only solution that works. In my test, all three methods returned a different number for `0`. – Alan H Nov 23 '16 at 06:48

13 Answers13

23

The following is even more efficient:

int findn(int num)
{
   if ( num < 10 )
      return 1;
   if ( num < 100 )
      return 2;
   //continue until max int
}

You could optimize this even further by doing a binary search, but that would be overkill.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Very good idea! You can do this even faster if you rearrange the order of checks (binary search). – Bolo Mar 15 '12 at 13:56
  • 1
    That's still `O(n)`, no? The number of comparisons required increases linearly with the size of the `int`; same as `strlen()` iterating through an array looking for `\0` – Brian Roach Mar 15 '12 at 13:56
  • 2
    @BrianRoach this is O(1) because the size of `int` is limited. As is the `strlen()` variant. They're actually all O(1). But I somehow think this will be faster. – Luchian Grigore Mar 15 '12 at 13:58
  • 1
    This (as well as the others, I believe) is `O(k)`, where `k` is the number of bits required to represent `num` as an integer. – Aaron Dufour Mar 15 '12 at 14:11
  • @AaronDufour the `O` notation only applies to values that can get (theoretically) near infinite. Since `k` has a maximum (even if that maximum is 32 or 64 or 10000), it's still `O(1)`. – Luchian Grigore Mar 15 '12 at 14:26
  • @LuchianGrigore Actually, its quite common to talk about the size of the input even when its not theoretically infinite, particularly if its the only meaningful factor in the time complexity. Sure, it may not be technically correct, but that doesn't mean its not a useful measure. – Aaron Dufour Mar 15 '12 at 15:13
  • @AaronDufour in this case, it is a useless measure. Because the size of the input doesn't vary from 1 to 1000000, but from 1 to 10. – Luchian Grigore Mar 15 '12 at 15:15
  • @LuchianGrigore So 3 orders of magnitude don't matter, but 20 do? Where's the cutoff? Also, you will find up to 7 orders of magnitude on many modern systems (`int` isn't necessarily 4 bytes). `O(k)` is a useful measure, since it tells us that the magnitude of the input changes how long it takes to run, which is what you generally want to know from big-O. – Aaron Dufour Mar 15 '12 at 15:22
  • @AaronDufour The cutoff is 4. :) Just kidding. I'm saying 3 orders of magnitude don't make a difference. 20 do. Also, I can just say that the maximum time this runs in is 10 (it makes at least 10 comparisons), irrespective of the input size. 10 is a constant, ergo `O(1)`. – Luchian Grigore Mar 15 '12 at 15:38
  • @LuchianGrigore Do you still think these are O(1)? O(1) implies that they're constant in time, meaning there should be no difference in time between the first `return` and the last, but the abstract machine says the two conditions are separated by sequence points. You could make an O(1) function by removing the sequence points like this: `return (n >= 0) + (n >= 10) + (n >= 100) + (n >= 1000) + (n >= 10000);` (this is meaningless for negative `int` values, btw) but then you're changing the constant. Some O(1) functions are guaranteed to be slower than some O(n) functions. – autistic Jan 06 '16 at 08:33
  • @LuchianGrigore These functions are technically O(n) due to the presense of iteration over the digits of the string (even though you've unrolled the loop, which is just silly). – autistic Jan 06 '16 at 08:34
  • @Seb see previous comments, it's O(1). Algorithmic complexity doesn't mean you do 1 check or 5 checks, it's how it scales over the size of the input. For numbers from `0` to `INT_MAX`, it takes at most `number of characters in INT_MAX` steps, so it's limited by a constant (as opposed to calculating factorials of a number between `0` to `INT_MAX`, which scales quite differently). I agree it's a micro-optimization and that you should go for readabilty, but it still answers the question. The edge-case for negatives can be easily solved by a `* -1`... – Luchian Grigore Jan 06 '16 at 09:46
  • @LuchianGrigore Consider that objects are limited in size by a constant in C; they are at most `SIZE-MAX` bytes, so `strlen` is limited to inspecting that many bytes. Does this imply that `strlen` is O(1)? – autistic Jan 06 '16 at 17:25
  • 1
    Oh and before I forget to point it out, `* -1` might introduce undefined behaviour for some negative values. – autistic Jan 06 '16 at 22:56
  • @Seb strlen on an input limited in size by a constant is O(n). – Luchian Grigore Jan 10 '16 at 23:30
  • When you answered that question, did you considerthat the size limitation (`SIZE_MAX`) of an object (i.e. a string) is directly related to the size limitation of an `int`, and so your answer that `strlen` is O(n) contradicts your belief that the code above is O(1)? **Unrolling a loop to an upper-bound doesn't reduce the complexity; otherwise we could unroll the loop of `strlen` to `SIZE_MAX` tests and this would reduce `strlen` to O(1).** – autistic Jan 16 '16 at 00:54
  • 1. I said "number of characters in `SIZE_MAX`" which is a small number. 2. Complexity refers to growth of the computation time related to growth in the input provided. When you limit the input, you limit the growth in computation time. By definition, if computation time is bound (in this case, by the time it takes to process the **maximum** input available), then it's constant. – Luchian Grigore Jan 18 '16 at 09:18
  • If your code inspects one digit (from the same integer) with an opportunity for `return` followed by another digit (from the same integer) with another opportunity for `return`, execution might not reach the second comparison in order for that second `return` to occur. I'm surprised I have to point this out. There is one branch that returns one (actually a number of) sequence point/s sooner than the other, so this function can't be O(1) in constant time complexity. – autistic Jul 05 '18 at 22:02
  • The cleaner way to write this is `return n < 10 ? 1 : n < 100 ? 2 : ...` aligning the `?`s below the `n` from `return`. Of course, the sequence points are *still there*, short-circuit evaluation still applies (i.e. where the first branch is taken, the rest won't be evaluated). You could do the same with `strcpy` i.e. `if (!str[0]) { return 0; } if (!str[1]) { return 1; } ... if (!str[SIZE_MAX]) { return SIZE_MAX; }` or `size_t strlen(char const *str) { return !str[0] ? 0 : !str[1] ? 1 : !str[2] ? 2 : ... SIZE_MAX; }`... how could that be *faster*? – autistic Jul 05 '18 at 22:13
11

The GCC/Clang __builtin_clz() or Microsoft Visual C _BitScanReverse() intrinsic functions compile to a single machine instruction on many machines. You can use this as the basis for an O(1) solution. Here's a 32-bit implementation:

#include <limits.h>
#include <stdint.h>

/* Return the number of digits in the decimal representation of n. */
unsigned digits(uint32_t n) {
    static uint32_t powers[10] = {
        0, 10, 100, 1000, 10000, 100000, 1000000,
        10000000, 100000000, 1000000000,
    };
    static unsigned maxdigits[33] = {
        1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5,
        5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 
    };
    unsigned bits = sizeof(n) * CHAR_BIT - __builtin_clz(n);
    unsigned digits = maxdigits[bits];
    if (n < powers[digits - 1]) {
        -- digits;
    }
    return digits;
}
Gareth Rees
  • 64,967
  • 9
  • 133
  • 163
AShelly
  • 34,686
  • 15
  • 91
  • 152
  • How would you do that for uint64_t? – Dereckson Sep 21 '15 at 20:56
  • 1
    The `maxdigits` table is `[len(str(pow(2,b))) for b in range(0,MAX_BITS)]`. Extend the `powers` table so that it has entries up to 10^19. That should do it. – AShelly Sep 21 '15 at 21:06
  • I noticed that the OP expects input in the form of `int`. The question doesn't explicitly state whether or not negative numbers are expected, and indeed some of his alternatives count the sign and others don't. It's nice to see a different alternative, for future viewers, but I can't help but feel the standard-compliant O(1) version might be appropriate: replace `__builtin_clz(n)` with the appropriate shifts and additions, or comparisons, i.e. something like `unsigned bits = sizeof n * CHAR_BIT - (n <= 0x7FFFFFFF) - (n <= 0x3FFFFFFF) - (n <= 0x1FFFFFFF) - ...`. – autistic May 29 '17 at 02:45
  • 1
    Instead of that change, just add `if (n<0) { n=-n;extradigits=1;} at the beginning. Add extradigits to the digits sum if you want to include space for the minus sign. – AShelly May 29 '17 at 03:38
  • 1
    @AShelly `n=-n;` is potentially undefined behaviour... so no, don't do that. – autistic May 20 '18 at 11:59
  • Your solution is undefined for 0. __builtin_clz is defined only for non-null values. https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html – Zoltan Tirinda Mar 12 '19 at 14:53
  • `if (!n) return 1`? – AShelly Mar 12 '19 at 16:53
9

As it currently stands, the accepted and most highly approved answer is (still) incorrect for negative numbers. If the answerer were to take the time to test it and find out that it's broken for negative numbers, he likely would have wasted more time than the machine ever would by simply using snprintf, i.e.

int count_digits(int arg) {
    return snprintf(NULL, 0, "%d", arg) - (arg < 0);
}

We're not in the 1980s anymore; stop coding as though we are. I'm a C-standard zealot and my favourite answer given here was Tao Feng's answer... but even that didn't go into why it's the most efficient answer so far; in this answer I intend to show that his answer can be improved further by considering the following:

  • Programmer productivity is more important than code efficiency, because it will almost certainly cost more time to write and test new functions properly than it will for a few microseconds of runtime.
  • Reusing the same standard library functions that other programs commonly use (probably) keeps those standard libraries in the CPU cache. A cache miss (for example, when your code needs to be copied from RAM into the CPU) can cost up to 50 CPU instructions, not to mention the other code may end up causing another cache miss to put snprintf back into the cache anyway.
  • Eliminating storage requirements might expose extra optimisations.

The following describes the micro-optimisation which hinders your productivity. Due to the lack of information you've provided in your answer, nobody who answers the question as it currently stands can provide any proof without making assumptions about:

  • When we optimise we need to find the most significant bottleneck in the full solution (the problem that your program's designed to solve). There are two possibilities here: A) You want to calculate the number of bytes to allocate in order to store a string containing these digits; B) You just want to count the number of digits or whatever for kicks. More on these later. For now it's important to realise you're probably talking about part of a solution, and that part might not be the most significant bottleneck.
  • The compiler you're using, the OS you're using and the machine you're using (including RAM speed, since some of us are introducing potential cache misses that are affected more by slow memory than by fast memory) might affect the most significant bottleneck. Some compilers are different to others, and will optimise some pieces of code better for some OSes, CPUs, etc than others.

You can avoid micro-optimisation by measuring bottlenecks, i.e. by profiling ("benchmarking") each of these solutions on your system, assuming they even solve your problems properly. If a solution doesn't solve the problem, it's not a solution, so it shouldn't be considered... When done correctly this should eliminate the micro-optimisation. Some compilers even provide intelligent profile-guided optimisation which commonly shaves 20-30% by reorganising branches and objects for cache locality, and do so automatically.

I've already covered counting digits, which I think most certainly answers your question, but there are cases where you might think you need to count digits when you don't, and the ability to remove the overhead of counting digits might present a very desirable optimisation, both in man hours and in machine hours.

For example, if you want to calculate the number of bytes to allocate in order to store a string containing these digits, you shouldn't use any runtime because a preprocessor macro can be used to calculate the maximum number of digits (or characters, including the sign), and any precious bytes of temporary storage you try to save will be well outnumbered by machine code bytes added in logic, which seems like a steep cost to me. There's also a benefit for the programmer to use a preprocessor macro; the same macro could be used for any integer type. See my answer to this question for a solution to this problem; after all, there's no point repeating myself...

autistic
  • 1
  • 3
  • 35
  • 80
  • What about option C? Why is snprintf better than log? – Sandeep Datta Dec 25 '17 at 07:24
  • @SandeepDatta I'd hate to repeat myself, but you asked for it... *"Notice how this function requires no intermediate storage, which is an important consideration in potential cache locality."* ... *"I don't need to test this, because I already know that `(arg < 0)` will only subtract the sign byte when `arg` is less than 0, and because I know how `snprintf` works. Thus, I'm already most likely miles ahead of the top answer in productivity time and CPU time due to lack of testing required."* – autistic Dec 26 '17 at 10:35
  • @SandeepDatta Take the same signature as my function, and write your `log` version without declaring any extra variables... perhaps then you'll understand what I mean by "intermediate storage", and why `snprintf` truly is better than `log` for this purpose. – autistic Dec 26 '17 at 10:38
  • @SandeepDatta Also, since your `log` version will involve a conversion from `int` to `double` (which is a floating-point type), here's [what every computer scientist should know about floating-point arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). As there's a conversion from `int` to `double` for the conversion of `x` in `log(x)`, there's also going to be a conversion from `double` back to `int` for the conversion in `int y = log_x_expression;`. For both of these conversions, [C11/6.3.1.4p2](https://port70.net/~nsz/c/c11/n1570.html#6.3.1.4p2) states the following: – autistic Dec 26 '17 at 10:47
  • @SandeepDatta *"If the value being converted is outside the range of values that can be represented, the behavior is undefined."*... How do you know if your `int` value is within range for `double` or not? Also, how do you handle negative numbers? Have you tested that? Have you planned for it? I don't need to plan or test, because... I know how `snprintf` will count them... Feel free to waste several hours planning and testing. In the meantime, your bottleneck is growing by those several hours, and as those are man hours they cost even more money than CPU clock cycles... ***Convinced, yet?*** – autistic Dec 26 '17 at 10:51
  • How do you know `snprintf` does not use any "intermediate storage" internally? I think you are over thinking it. Why would you need several hours to plan and test such a simple function? Also you can just add a compile time check to ensure `int` can be represented by a `double` for your target platform. – Sandeep Datta Dec 27 '17 at 14:41
  • @SandeepDatta [What is `log10(1)`?](https://ideone.com/ahHxza)... Now, as I requested earlier, please write your (working) `log10` version of `count_digits` so we can compare the two, working to working, rather than working to buggy. – autistic Dec 27 '17 at 20:06
  • I'd up-vote this answer if you presented your final solution first. Some people may find your answer unnecessarily verbose and exasperating otherwise. – Sandeep Datta Jan 03 '18 at 08:37
  • It is not through emotional impatience that one hones fine knowledge; that is their affliction, and the world won't stop spinning. Thanks for the suggestion, though. I've made some drastic changes, cut a lot of probably unnecessary (and somewhat invalid) content out... It reads kind of out of line, now, but I'll ponder on that in the coming hours. Have a happy new year :) – autistic Jan 03 '18 at 09:37
  • The original question assumed the numbers were not negative; you can see this because only 2 of the 3 methods returned reasonable values. – jorgbrown Feb 13 '18 at 21:37
  • @jorgbrown For a start, the proper way to communicate such an assumption would be using one of the `unsigned` types. Keep in mind, this is a site where questions are often asked about invalid/broken pieces of code. You can not assume the validity of code in a question, but you should absolutely be able to assume the validity of code in answers... Also keep in mind, when other visitors come here looking for an answer to the question with negative values supported, they would be disappointed by all answers above mine, would they not? – autistic Feb 14 '18 at 14:58
8

I think maybe you can write the first method as

int findn(int num)
{
    char snum[100];    
    return  sprintf(snum, "%d", num);
}

because sprintf will return the number of chars written and you can save the call to strlen.

As for the efficiency, I think it depends on the implementation of sprintf, you may need to find the source of sprintf and see if it is efficiency of doing this.

Tao Feng
  • 89
  • 2
3

One liner: for(digits = 0; num > 0; digits++) num /= 10;

SatoriAmagi
  • 31
  • 1
  • 1
3

I think sprintf() will use your method 2 to print the number (to determine the length of the string to print, and then to print each character of the string), so it will be inherently slower.

Number 3 would probably involve some polynomial approximation of ln() wich will involve more that 1 division, so I guess it will be slower as well (here's a fast ln() implementation, still involving float division... so WAY slower).

So my tentative guess is, method 2 is the way to go.

Please note that this is a quite liberal way to approach this problem. I guess testing a good old timed million-iteration with each function will tell you the result. But it would bee too bruteforce, wouldn't it?

Note that only method 2 will give you the real results, the others have flaws that have to be adjusted to be correct (see Aaron's answer). So simply pick method 2.

Gui13
  • 12,993
  • 17
  • 57
  • 104
  • What's wrong with brute force? If the problem is small in size, brute force is often the most efficient way to go. – Luchian Grigore Mar 15 '12 at 13:57
  • Yeah, but I'd rather understand *why* method X is faster than Y. Just thinking about the problem, you can already see that sprintf() will be intrinsectly slower, for instance. – Gui13 Mar 15 '12 at 14:01
3

Try binary search. For the sake of clarity, let's assume signed 32-bit integers. First, check if x < 10000. Next, depending on the answer, if x < 100 or x < 1000000, and so on.

That's O(log n), where n is the number of digits.

Bolo
  • 11,542
  • 7
  • 41
  • 60
  • 1
    I think that for 32-bit integers, the simplicity of method 2 (which uses at most 11 divisions then) will beat binary search. – Daniel Fischer Mar 15 '12 at 13:55
  • 1
    This method uses up to 4 or 5 *comparisons* instead of up to 11 divisions (Luchian deserves the credit for this crucial observation). – Bolo Mar 15 '12 at 14:00
  • Yes, but you're branching to different comparisons, while method 2 has just one fixed jump on comparison with 0 (and the divisions should be transformed to multiplies and shifts by the compiler if that's faster on the given CPU). My gut feeling is that for so few operations the predictability wins over fewer operations. I won't be surprised if I guessed wrong, however. – Daniel Fischer Mar 15 '12 at 14:09
3

These functions give drastically different results for non-positive numbers (the worst is method 3), so comparing their time complexities is of dubious value. I would use the one that gives the answer required in all cases; without context, we can't know what that is (it's probably not method 3).

For method 1, findn(0) == 1, and findn(-n) == digits in n + 1 (because of the negative sign).

For method 2, findn(0) == 0, and findn(-n) == digits in n.

For method 3, findn(0) == INT_MIN, and findn(-n) == INT_MIN as well.

Aaron Dufour
  • 17,288
  • 1
  • 47
  • 69
  • While not an answer to the question, you do have a point... in order to optimise something we first need to ensure that what we're optimising is valid... otherwise it's an invalid optimisation. – autistic May 19 '18 at 15:53
  • @Sebivor Not an answer to the question asked ("which is most efficient"), perhaps, but I wrote it as an answer rather than a comment because I believe it _is_ an answer to the question implied ("which should I use"). – Aaron Dufour May 20 '18 at 07:54
  • Yeh... The bug-free version is almost always going to be more efficient, because it means you won't have to allocate man hours to debugging. That's kind of what I meant by *invalid optimisation*; it *seems* like an optimisation, but ends up costing more time later on... – autistic May 20 '18 at 11:53
1

This is my solution... it'll count up to 100 digits or you know whatever you want it to count up to.

max_digits = 100

int numdigits(int x) {
  for (int i = 1; i < max_digits; i++) {
    if (x < pow(10, i)) {
      return i;
    }
  }
  return 0;
}
  • **1.** You can pre-calculate `pow()` for speed: `int pow10[max_digits]; pow10[0] = 1; for (int i = 1; i < max_digits; i++) pow10[i] = 10 * pow10[i-1];`. **2.** Further speed gain: binary search. – 7vujy0f0hy Nov 18 '17 at 14:32
  • @7vujy0f0hy That's only meaningful if you have profiler reports to show that it's actually faster, right? Otherwise this is all premature optimisation, right? – autistic May 19 '18 at 15:51
  • @Sebivor: No idea, the problem was out of my mind for so many months I forgot what I was thinking. So you could be right. Or I could be right. Good luck with finding the best solution. P.S. At least mine doesn’t require `#include `, right? – 7vujy0f0hy May 20 '18 at 04:41
  • @7vujy0f0hy Do you suppose if you use too many lookup tables, they might fill your CPU cache lines, leaving less room for other data (and causing cache misses)? Following on... it's only an optimisation if you have proof that it speeds things up, right? What if it slows other things down more than it speeds anything up? Is it still an optimisation? ;) – autistic May 20 '18 at 11:49
  • @Sebivor: Well, the original `pow()` is a floating point operation, so it’s unlikely to be faster than a very small integer lookup table. I can’t be absolutely certain of this without a proof, it’s merely common sense logic, which is fallible. I have never implemented IRL what I suggested there. – 7vujy0f0hy May 21 '18 at 19:30
1

printf function will return the number of digits successfully printed.

int digits,n=898392;

digits=printf("%d",n);
printf("Number of digits:%d",digits);

Output:

898392

Number of digits:6

Asha
  • 750
  • 1
  • 6
  • 22
0

Using log may be a good option...

  1. If the target machine has hardware support for it
  2. If you are sure that int can be cast into double and back without any loss of precision.

Sample implementation...

int num_digits(int arg) {
    if (arg == 0) {
        return 1;
    }

    arg = abs(arg);

    return (int)log10(arg)+1;
}
Sandeep Datta
  • 28,607
  • 15
  • 70
  • 90
  • Note how [according to the manual, `log10` requires a `double` argument](http://pubs.opengroup.org/onlinepubs/9699919799/functions/log10.html). There's still an implicit conversion from `int` to `double` happening here; you can make that conversion explicit by introducing a cast here: `return (int)log10((double)arg)+1;` and the problem with both of these conversions (aside from the overhead they incur) is that when `int` has a range that extends beyond that of `double`, undefined behaviour is possible. Thus, this approach is, as far as I'm aware, not portable in the realms of C. – autistic Dec 31 '17 at 12:24
  • The relevant section for my previous mention of undefined behaviour is [here](https://port70.net/~nsz/c/c11/n1570.html#6.3.1.4p2), there may also be some other things of interest to note from that paragraph. How do you count the sign character? I know you claim this is optimal but if you can't guarantee that it's correct on every system to begin with, how is this better than an inoptimal version? What if a bug were to surface in a mission critical situation that seemed to be triggered spontaneously because of that conversion? What of those who use 64-bit `int` (ILP64 and SILP64) and IEEE754? – autistic Dec 31 '17 at 13:12
  • Updated my answer to address your concern. I agree the `snprintf` solution will work everywhere and is more "cross-platform/future proof" than using log10 but there are times when you are not looking for the most general solution. A platform specific solution can be more optimal even if it is not portable. – Sandeep Datta Jan 03 '18 at 08:31
  • "How do you count the sign character?" The question does not say anything about counting the sign character. – Sandeep Datta Jan 03 '18 at 08:31
0

log10(x) + 1 will count the digits for x > 0, but another approach may be desired:

  1. log2(x) = size_of(x) - 1 - clz(x) will calculate log2 as an integer.
  2. log_b(x) = log2(x) / log2(b) will do a base conversion.
  3. The log2(10) divisor becomes 3 for base 10.
  4. The log2(x) dividend becomes 31 - clz(x) for 32-bit integers.

You can even count hex digits or any other base with this.

uint32_t count_dec_digits(uint32_t n) {
    return 1 + (31 - __builtin_clz(n)) / 3;
}

uint32_t count_hex_digits(uint32_t n) {
    return 1 + (31 - __builtin_clz(n)) / 4;
}

int main() {
    printf("%d", count_dec_digits(1234567)); // 7
    printf("%d", count_hex_digits(0x12345)); // 5
}
jamen
  • 1
  • It's good to see people getting enthusiastic with tech. However note that, the question asks for the "time complexity" of method 1 and 3, not another method to count the digits. Even if so, your program makes several mistakes (integer divisor for decimal case, use of implementation-specific built-in, and implicit cast of uint32_t to int). – DannyNiu May 06 '20 at 07:50
  • Thanks for the input. I came up with the method in a different language but figured I'd try to contribute it in C, which I'm not very experienced in. – jamen May 08 '20 at 14:17
0

Variant of the function suggested by @AShelly for signed and unsigned 64 bits numbers with the check for 0 (https://godbolt.org/z/cqeKEj4rj, contains also some testing code).

uint8_t digits_u64(uint64_t number) {
    static uint8_t maxdigits[] = {
        1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6,
        7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11,
        12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15,
        16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20, 
    };

    static uint64_t powers[] = {
        0U, 1U, 10U, 100U, 1000U, 10000U, 100000U, 1000000U, 10000000U,
        100000000U, 1000000000U, 10000000000U, 100000000000U,
        1000000000000U, 10000000000000U, 100000000000000U,
        1000000000000000U, 10000000000000000U, 100000000000000000U,
        1000000000000000000U, 10000000000000000000U, 
    };

    if (number == 0) {
        return 1;
    }

    unsigned bits = sizeof(number) * CHAR_BIT - __builtin_clzll(number);
    unsigned digits = maxdigits[bits];

    if (number < powers[digits]) {
        --digits;
    }
    
    return digits;
}

uint8_t digits_i64(int64_t number) {
    int add_minus_sign = number < 0U ? 1 : 0;
    if (add_minus_sign) {
        number *= -1;
    }

    return digits_u64(number) + add_minus_sign;
}
Daniele Salvatore Albano
  • 1,263
  • 2
  • 13
  • 29