0

I am looking for a very simple C++ class implementing an unsigned integer with arbitrary precision and just the post increment operator.

I know there are library for arbitrary precision integer arithmetic but my needs are quite simple and I prefer to avoid the weight of a full library.

It seems to me that my current implementation is still not simple enough and not elegant enough. What do you suggest?

#include <vector>
#include <string>
#include <algorithm>

class UNat
{
public:
    static const char base = 10;
    UNat( const char* n )
    {
        std::string s(n);
        std::reverse(s.begin(),s.end());
        for ( size_t i = 0; i < s.length(); i++ ) {
            n_.push_back(s[i]-'0');
        }
    }

    UNat& operator++(int) 
    {
        bool carry = false;
        bool finished = false;

        for ( size_t i = 0; i < n_.size() && !finished; i++ ) {
            n_[i] = add(n_[i],1,carry);
            if ( carry ) {
                finished = false;
            } else {
                finished = true;
            }
        }
        if ( carry ) {
            n_.push_back(1);
        }
        return *this;
    }

    std::string to_string() const
    {
        std::string r(n_.begin(), n_.end());
        std::reverse(r.begin(),r.end());
        std::for_each(r.begin(), r.end(), [](char& d) { d+='0';});
        return r;
    }

private:
    char add( const char& a, const char& b, bool& carry )
    {
        char cc = a + b;
        if ( cc >= base ) {
            carry = true;
            cc -= base;
        } else {
            carry = false;
        }
        return cc;
    }
    std::vector< char > n_;    
};

std::ostream& operator<<(std::ostream& oss, const UNat& n)
{
    oss << n.to_string();
    return oss;
}

#include <iostream>

int main()
{
    UNat n("0");
    std::cout << n++ << "\n";
    std::cout << UNat("9")++ << "\n";
    std::cout << UNat("99")++ << "\n";
    std::cout << UNat("19")++ << "\n";
    std::cout << UNat("29")++ << "\n";
    std::cout << UNat("39")++ << "\n";
    return 0;
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Alessandro Jacopson
  • 18,047
  • 15
  • 98
  • 153
  • 4
    Post increment is supposed to increment the value *and* yield the original value as expression result. It seems that your post-increment operator yields the modified value. Is that the problem you want help with? – Cheers and hth. - Alf Dec 21 '13 at 13:18
  • I guess if all you're doing is increment, ripple-carry isn't so bad... Might be good to do things on 32-bit or 64-bit primitives instead of 8-bit but 8-bit does keep it simple. –  Dec 21 '13 at 13:36
  • @ebyrob: I never found a good description of Babbage's predictive scheme, but Pascal (before him) used a nice gravity assist scheme. Essentially each digit wheel had to be primed by hand before the addition. Then any carry would just fling something at the next digit wheel, giving it an extra little push, and these throwing actions could proceed in parallel. Ingenious, evidently. Except I didn't understand how it could do away with sequencing. :( – Cheers and hth. - Alf Dec 21 '13 at 13:39
  • 1
    If all you want to do is increment it, you hardly need infinite precision. At 1ns per increment, a 64 bit long will take you at least 10^9 seconds to overflow. Then you can use a C++ long and the standard post-increment operator. – Ira Baxter Dec 21 '13 at 13:46
  • @Ira: `long` is only guaranteed to be 32 bits, so `long long` would be more like it. – Steve Jessop Dec 21 '13 at 14:26

2 Answers2

1

In order to avoid returning the mutated value, save a local copy and return it:

UNat operator++(int) 
{
    UNat copy = *this;
    // ....
    return copy;
} // don't return by reference

Comparatively, the prefix operator does return by reference.

UNat& operator++ ()
{
  // ....
  return *this;
}

Some tips and tricks from Arbitrary-precision arithmetic Explanation:

1/ When adding or multiplying numbers, pre-allocate the maximum space needed then reduce later if you find it's too much. For example, adding two 100-"digit" (where digit is an int) numbers will never give you more than 101 digits. Multiply a 12-digit number by a 3 digit number will never generate more than 15 digits (add the digit counts).

An alternative implementation for your addition function can look like this:

c->lastdigit = std::max(a->lastdigit, b->lastdigit)+1;
carry = 0;

for (i=0; i<=(c->lastdigit); i++) {
    c->digits[i] = (char) (carry+a->digits[i]+b->digits[i]) % 10;
    carry = (carry + a->digits[i] + b->digits[i]) / 10;
}
Community
  • 1
  • 1
1

The code implements what's generally known as Binary Coded Decimal (BCD). Very simple, and, as you say, the implementation can be much simpler if you only want to increment and don't need general addition. To simplify even further, use the internal character representations for the digits '0' through '9' instead of the values 0 through 9. And for another simplification, store the characters in a std::string:

for (int index = 0; index < digits.size(); ++index) {
    if (digits[index] != '9') {
        ++digits[index];
        break;
    }
    digits[index] = '0';
}
if (index == digits.size())
    digits.push_back('1');

This makes the stream inserter nearly trivial.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • If you want to use different radices, you can do this using binary and just and if-then-else on the current-bit-value. But all this does is make a slow version of infinite precision. – Ira Baxter Dec 21 '13 at 14:31
  • @IraBaxter - there are lots of bells and whistles that could be added. The question was how to make it simple, and this is the simplest that I can see. In particular, supporting bases greater than 10 means writing much more complicated code to increment individual elements. – Pete Becker Dec 21 '13 at 14:32
  • Supporting radixes greater than ten is trivial. For radix r, use digits valued 0 to r-1. Your if statement becomes "digits[index]!=". If you choose r=10^n (you chose n=1) then printing the digits is easy. – Ira Baxter Dec 21 '13 at 14:36
  • @IraBaxter - if you do that the stream inserter becomes more complicated; it can no longer simply copy existing character values. Again: there are lots of things that could be added at the cost of more complexity. My goal was simplest, not merely simple. If you have other goals, by all means post your own answer. – Pete Becker Dec 21 '13 at 14:38
  • @Yakk - the highly authoritative site [Wikipedia](http://en.wikipedia.org/wiki/Binary-coded_decimal) doesn't agree. It distinguishes **uncompressed** BCD and **packed** BCD. – Pete Becker Dec 21 '13 at 14:48
  • @petebecker I guess I should go fix that ;) – Yakk - Adam Nevraumont Dec 21 '13 at 16:33