4

Background

I have a logging system that outputs records to a std::ostream. Each record is annotated with a counter that is increased by one with each output, like this:

=====  Batch # 5  =====
  This is the fifth record
=====  Batch # 19  =====
  This is the nineteenth record
=====  Batch # 187  =====
  Who knows to spell *that*?

The counter is a std::size_t, i.e., an unsigned integer.

Problem

As it is now, numbers are outputted without any padding, which looks ugly. I would like to achieve this:

=====  Batch #     5  =====
  This is the fifth record
=====  Batch #    19  =====
  ...
=====  Batch #   187  =====
=====  Batch # 12345  =====

Making the stream object handle this can be achieved with std::setw. However, I still need to know how many digits takes the base-10 representation of the maximum possible value.

Request

The solution must compute statically (i.e. at translation-time) how many digits takes the base-10 representation of the maximum possible value of a std::size_t. I will welcome any method that meets this requirement, although I will prefer those that employ C++14's (relaxed) constexprs.

Hints and things I have tried

It seems that the representation of a natural number N in base-B takes as many digits as the truncation of (the B-index logarithm of N) plus 1. For example, the value 100 is three digits long in base 10, and the common logarithm of 100 plus 1 is 3; the value 1234 is four digits long in base 10, and the truncation of (the common logarithm of 1234) plus 1 is 4. In pseudo-something:

digits( value , base ) = floor( logarithm( radicand=value , index=base ) ) + 1

I apologize for the lazyness of the explanation and the formatting; if someone knows a better way, please, feel free to edit my post or to suggest it. You can try this here. It seems to work, but I haven't thoroughly verified it.

As for the maximum value a std::size_t object can hold, it is std::numeric_limits<std::size_t>::max().


I intend to answer this question. I will give some time before posting my answer, and wait more before accepting any.

djsp
  • 2,174
  • 2
  • 19
  • 40
  • 2
    Are you really going to have 2^64 batches? How many universe's lifetimes worth of computing are you going to do? Why not just pick a sensible value like 10 or 15 or so. – Kerrek SB Dec 13 '14 at 14:41
  • @KerrekSB It's just a fixation for doing things 'right'. The background is provided to better explain the question, which is purely theoretical. I agree with you that anyone pragmatic would just choose some good value. – djsp Dec 13 '14 at 14:47
  • Why not just see how many times you can divide the max value of size_t. That will be constexpr in C++14. Though, it seems that the answer from ForEveR is better. – kec Dec 13 '14 at 15:11
  • @kec You are right, that would be way simpler! I think it could even be implemented using recursion, if one were to write in C++11. However, the `std::numeric_limits` solution is still much easier :). – djsp Dec 13 '14 at 15:22
  • Though, if this is just an academic question, if you want a short, fun exercise, try writing a function to convert int to ASCII. It's not hard, and you can even do it recursively to make it even more concise. – kec Dec 13 '14 at 15:24

2 Answers2

7

Seems that std::numeric_limits::digits10 is what you want. live example

ForEveR
  • 55,233
  • 2
  • 119
  • 133
  • 1
    Are you sure? The description on cppref makes it unclear to me whether it would report `10` (for the max case) or `9` (for the all fits case) for a 32bit integer -- and indeed [it returns 9](http://coliru.stacked-crooked.com/a/7eab6ab47374bebf) but the question would need 10 to accomodate all numbers. – Martin Ba Dec 13 '14 at 15:02
  • SO for all types (which would be all) where `max()` isn't a `...9999` value, you need to add 1 to `digits10` I think. – Martin Ba Dec 13 '14 at 15:03
  • @MartinBa: Then just add 1 to it. – kec Dec 13 '14 at 15:12
1

The C++ Standard Library provides the std::numeric_limits class template, which may be used to query various properties of arithmetic types. Among them is the number of base-10 digits that can be represented by each type, which is encoded in the digits10 static member, a constexpr int. However, and as outlined by Martin Ba in his comments, it is necessary to add 1. Do this:

template<class T>
constexpr int maximum_base10_digits =
    std::numeric_limits<T>::digits10 + 1;

It will also ensure that the value is computed at translation-time (if you simply pass the value to, say, std::setw, the addition could be performed at runtime... a terrible slowdown!).

Although the corresponding cppreference.com page does not list any specialization for std::size_t, it is not a problem, as stood out in the std::numeric_limits page linked above:

The standard library types that are aliases of arithmetic types (such as std::size_t or std::streamsize) may also be examined with the std::numeric_limits type traits.

Additionally, this approach has been available since C++11 (or C++98, if you are willing to drop the constexpr), which makes it compatible with many more C++ implementations. Just don't make it a variable template if there's no C++14.

#include <cstddef>
#include <iomanip>
#include <iostream>
#include <limits>


namespace
{
    template<typename T>
    void
    pretty_print
    (
     const T value
    )
    {
        // Ensure that it's computed at compile-time
        constexpr int maximum_digits = std::numeric_limits<T>::digits10 + 1;

        std::cout << std::setw( maximum_digits ) << value << '\n';
    }
}

int
main
( )
{
    pretty_print( std::size_t{ 7 } );
    pretty_print( std::numeric_limits<std::size_t>::max() );
}

As a side note, do not get confused with the C++11 addition std::numeric_limits::max_digits10 (see this question). If you have a look at the list of specializations provided by the Standard Library, you'll note that its value is 0 for all integer types.

Community
  • 1
  • 1
djsp
  • 2,174
  • 2
  • 19
  • 40