the most efficient way to calculate the number of digits from 1 to n

Pages: 12
Jan 28, 2012 at 6:33pm
Fair enough.
Jan 28, 2012 at 6:56pm
Had overlooked Fredllman's post when I made mine, or I wouldn't have made it. The same idea, and the Fredllman implementation is clearly better than mine.
Jan 29, 2012 at 4:42am
Duoas: BOOM! Enjoyed!
Jan 29, 2012 at 5:16am
Duoas, that is... what is that?
Jan 29, 2012 at 6:44am
What "that"?
That awful bignum I scrambled up so no one would consider it a work of art?
Or the fun thinking about how to calculate the answer to the OP's question?

Come to think of it, the log10() is not necessary, since we already know it from the user's input... and there are efficient integer pow() functions...

Let me check this out...


OK
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//----------------------------------------------------------------------------
// JLBorges
unsigned long ndigits( unsigned long number )
{
    unsigned long digits = 0 ;
    unsigned long nd = 1 ;

    for( unsigned long i = 9 ; number > i ; i *= 10 )
    {
        digits += i*nd ; ++nd ; number -= i ;
    }

    return digits + number*nd ;
}

//----------------------------------------------------------------------------
// Fredllman
unsigned long numDigits( unsigned long n ) {
    unsigned long result = 0;
    
    for (unsigned long p = 1; p <= n; p *= 10) {
        result += n - p + 1;
    }
    
    return result;
}

//----------------------------------------------------------------------------
// Duoas
unsigned long ipow( unsigned long base, unsigned long exp )
  {
  unsigned long result = 1;
  while (exp)
    {
    if (exp & 1)
      result *= base;
    exp >>= 1;
    base *= base;
    }
  return result;
  }

unsigned long digits_in_1_to_N( unsigned long n, unsigned long w )
  {
  if (n == 0) return 0;

  n *= w;
  w -= 1;
  n -= (((ipow( 10, w ) - 1) / 9) * 10) - w;

  return n;
  }

D:\prog\nines\times> a
Calculate the number of digits needed to count from 1 to n.
n? 489564266
iterations? 100000000
JLBorges
489564266 --> 4294967292
time = 0:05.3663 = 0:00.0000 per iteration
Fredllman
489564266 --> 4294967292
time = 0:04.9295 = 0:00.0000 per iteration
Duoas
489564266 --> 4294967292
time = 0:03.0107 = 0:00.0000 per iteration

D:\prog\nines\times>

input digit count > 7: I win!
input digit count < 8: Fredllman wins!

Hmm... I suppose a "most efficient" program would choose which algorithm to apply based upon the digit-width of the input and/or failure due to machine word limitations.

...
Jan 29, 2012 at 6:54am
I suppose a "most efficient" program would do a table look up based on the number of digits for the sum of the first n-1 terms and then another table lookup to help compute the last term. It would have no loops at all.

Edit: Come to think of it, how does one compute the number of digits without a loop? Unroll it, I suppose.

Last edited on Jan 29, 2012 at 7:02am
Jan 29, 2012 at 7:24pm
By using a lookup table we can reduce it to just (standard integral types)

a. O( log3(D) ) integral comparisons where D is the maximum number of digits
b. One each of integral multiplication, addition and subtraction.

Example: For N < ten billion: 2 to 4 (average 3) comparisons, 1 subtraction, 1 multiplication, 1 addition (The rest of it is done at compile time).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <limits>

enum : long long { MAXDIGITS = std::numeric_limits<unsigned int>::digits10 } ;

template< unsigned int N > struct nines { enum : long long { value = nines<N-1>::value * 10 + 9 } ; } ;
template<> struct nines<1> { enum : long long { value = 9 } ; } ;
template<> struct nines<0> { enum : long long { value = 0 } ; } ;

template< unsigned int N > struct tens { enum : long long { value = tens<N-1>::value * 10 } ; } ;
template<> struct tens<1> { enum : long long { value = 10 } ; } ;
template<> struct tens<0> { enum : long long { value = 1 } ; } ;

template< unsigned int N > struct term { enum : long long { value = N * 9 * tens<N-1>::value } ; };
template<> struct term<1> { enum : long long { value = 9 } ; } ;

template< unsigned int N > struct sum_terms
{ enum : long long { value = sum_terms<N-1>::value + term<N>::value } ; };
template<> struct sum_terms<0> { enum : long long { value = 0 } ; } ;

template< unsigned int N > struct last_term
{
    unsigned long long operator() ( unsigned int n ) const
    { return ( n - nines<N-1>::value ) * N ; } };

template< unsigned int N > struct sum_plus_last_term
{
    unsigned long long operator() ( unsigned int n ) const
    { return sum_terms<N-1>::value + last_term<N>()(n) ; }
};

unsigned long long ndigits( unsigned int number )
{
    static_assert( std::numeric_limits<unsigned int>::digits10 < 10,
                   "this is example code for numbers smaller than 10 billion." ) ;

    if( number > nines<6>::value ) // 7,8,9 digits
        if( number > nines<8>::value ) // 9 digits
              return sum_plus_last_term<9>()(number) ;
        else if( number > nines<7>::value ) // 8 digits
              return sum_plus_last_term<8>()(number) ;
        else return sum_plus_last_term<7>()(number) ; // 7 digits

    else if( number > nines<3>::value ) // 4,5,6 digits
        if( number > nines<5>::value ) // 6 digits
              return sum_plus_last_term<6>()(number) ;
        else if( number > nines<4>::value ) // 5 digits
              return sum_plus_last_term<5>()(number) ;
        else return sum_plus_last_term<4>()(number) ; // 4 digits

    else // 1,2,3 digits
        if( number > nines<2>::value ) // 3 digits
              return sum_plus_last_term<3>()(number) ;
        else if( number > nines<1>::value ) // 2 digits
              return sum_plus_last_term<2>()(number) ;
        else return number ; // 1 digit
}

int main()
{
    std::cout << ndigits( std::numeric_limits<unsigned int>::max() ) << '\n' ;
}

Last edited on Jan 29, 2012 at 7:25pm
Jan 29, 2012 at 10:50pm
Nice!
Jan 30, 2012 at 12:55am
JLBorges, I can't get that to build. What am I missing?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
g++ 1toN.cpp                    - - - - - - - - - - - - - >
g++ -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5664~105/src/configure --disable-checking 
--enable-werror --prefix=/usr --mandir=/share/man 
--enable-languages=c,objc,c++,obj-c++ 
--program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ 
--with-slibdir=/usr/lib --build=i686-apple-darwin10 
--program-prefix=i686-apple-darwin10- 
--host=x86_64-apple-darwin10 
--target=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5664)
1toN.cpp:4: error: expected identifier before ‘:’ token
1toN.cpp:4: error: expected unqualified-id before ‘:’ token
1toN.cpp:6: error: expected identifier before ‘:’ token
1toN.cpp:6: error: expected primary-expression before ‘long’
1toN.cpp:6: error: expected ‘;’ before ‘long’
1toN.cpp:7: error: expected identifier before ‘:’ token
1toN.cpp:7: error: expected primary-expression before ‘long’
etc...

EDIT: Changed so line number matches yours
Last edited on Jan 30, 2012 at 1:13am
Jan 30, 2012 at 3:08am
You need to use the C++11 standard (-std=c++11 on the command line).

Your GCC is too old, so you can just hack out the stuff you don't need:
1 - get rid of the typed enum stuff enum : long long { ... } --> enum { ... }
2 - get rid of the static_assert

Good luck!
Jan 30, 2012 at 3:37am
I thought it was that.
Done.
Thanks.
$ ./a.out
Calculate the number of digits needed to count from 1 to n.
n? 4294967295
(notice how n is now too big for the machine method to work properly?)
unsigned int calculated --> 41838561849
bignum calculated       --> 41838561849
$ g++ 1toN.cpp 
$ ./a.out
4183856185
They are not equal? Is that because of me?
EDIT: Should the bignum be correct?
Last edited on Jan 30, 2012 at 3:56am
Jan 30, 2012 at 7:10am
If you have changed enum : long long { ... } to enum { ... }, you are creating an integer overflow. The number 41838561849 is outside the range of the C++98 enum.

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <limits>

int main()
{
    std::cout << "max possible: " << std::numeric_limits<unsigned int>::max() << '\n' ;
    unsigned long result = 41838561849ULL ;
    // *** warning: large integer implicitly truncated to unsigned type [-Woverflow]
    std::cout << result << " (how could we expect the result to be 41838561849)\n" ;
}


Topic archived. No new replies allowed.
Pages: 12