0

Im trying to convert my arbitrary precision integer class to be able to use digits that are not just 8 bits per digit. Ive stumbled upon a weird problem: I am able to use uint16_t for my base digit type, but not uint32_t. My code will return bad results. The example I used to find what was going wrong is 0x1111111111111111 * 0x1111111111111111, which should be0x123456789abcdf00fedcba987654321. However, I am getting 0x123456789abcdf0fedcba987654321.

I thought that I had changed all of the hardcoded types so that changing the base digit type would not matter, but apparently not.

Here is the relevant code:

typedef uint32_t                          digit;            // original code uses uint8_t; uint16_t works too
typedef uint64_t                          double_digit;     // int type to hold overflow values

typedef std::deque <digit>                base;

const digit NEG1 = -1;                    // uint8_t -> 255, uint32_t -> 4294967295
const digit BITS = sizeof(digit) << 3;    // sizeof gives the number of bytes, so multiply that by 8 to get the number of bits
const digit HIGH_BIT = 1 << (BITS - 1);   // uint8_t -> 128

        // left bit shift. sign is maintained
        integer operator<<(uint64_t shift){
            if (!*this || !shift)
                return *this;
            base out = digits;
            for(uint64_t i = 0; i < (shift / BITS); i++)
                out.push_back(0);
            shift %= BITS;
            if (shift){
                out.push_back(0);
                return integer(out, _sign) >> (BITS - shift);
            }
            return integer(out, _sign);
        }

        // right bit shift. sign is maintained
        integer operator>>(uint64_t shift){
            if (shift >= bits())
                return integer(0);
            base out = digits;
            for(uint64_t i = 0; i < (shift / BITS); i++)
                out.pop_back();
            shift %= BITS;
            if (shift){
                base v;
                for(d_size i = out.size() - 1; i != 0; i--)
                    v.push_front(((out[i] >> shift) | (out[i - 1] << (BITS - shift))) & NEG1);
                v.push_front(out[0] >> shift);
                out = v;
            }
            return integer(out, _sign);
        }

        // operator+ calls this
        integer add(integer & lhs, integer & rhs){
            base out;
            base::reverse_iterator i = lhs.digits.rbegin(), j = rhs.digits.rbegin();
            bool carry = false;
            double_digit sum;
            for(; ((i != lhs.digits.rend()) && (j != rhs.digits.rend())); i++, j++){
                sum = *i + *j + carry;
                out.push_front(sum);
                carry = (sum > NEG1);
            }
            for(; i != lhs.digits.rend(); i++){
                sum = *i + carry;
                out.push_front(sum);
                carry = (sum > NEG1);
            }
            for(; j != rhs.digits.rend(); j++){
                sum = *j + carry;
                out.push_front(sum);
                carry = (sum > NEG1);
            }
            if (carry)
                out.push_front(1);
            return integer(out);
        }

        // operator* calls this
        // Long multiplication
        integer long_mult(integer & lhs, integer & rhs){
            unsigned int zeros = 0;
            integer row, out = 0;
            for(base::reverse_iterator i = lhs.digits.rbegin(); i != lhs.digits.rend(); i++){
                row.digits = base(zeros++, 0); // zeros on the right hand side
                digit carry = 0;
                for(base::reverse_iterator j = rhs.digits.rbegin(); j != rhs.digits.rend(); j++){
                    double_digit prod = (double_digit(*i) * double_digit(*j)) + carry;// multiply through
                    row.digits.push_front(prod & NEG1);
                    carry = prod >> BITS;
                }
                if (carry)
                    row.digits.push_front(carry);
                out = add(out, row);
            }
            return out;
        }

Is there something obvious that I missed that might cause incorrect calculations? I have stared at this code for a little too long in one burst.

The full modified code is here.

EDIT: I have tested the code on ideone, and it is returning the correct value for this calculation, but my computer still does not. Is there any good explanation for this?

calccrypto
  • 8,583
  • 21
  • 68
  • 99
  • can you just use `boost::dynamic_bitset` or `std::vector` - with assumptions that left significant bit is at the beginning of it? – PiotrNycz Oct 21 '12 at 10:21
  • It looks like the problem is that the carry is not occurring on that overflow in the middle. Not sure what the cause is yet, but it might have to do with shifts (not all environments have consistent behavior wrt bit-shifts; MSVC likes to be a rebel). Which compiler/platform are you using? – WeirdlyCheezy Oct 23 '12 at 19:19
  • On a side note, compiling your ideone code with g++ 4.6.3 (with option -std=gnu++0x) on my Ubuntu box also produces 123456789abcdf00fedcba987654321. – WeirdlyCheezy Oct 23 '12 at 19:24

1 Answers1

2

The problem appears to be due to unintended excessive right-shifting. You mention that your ideone post doesn't reproduce the problem. While testing, I noticed that in your ideone post, the type of digit was actually still set to 16-bit. The moment I changed this to 32 bit (to reflect the SO code snippet) and tried to compile under gcc, I got a bunch of warnings about excessive right shift. Running the program resulted in an infinite loop, which, combined with the right-shift warnings and the fact that you get different behavior on your platform strongly suggests undefined behavior is occurring.

More specifically, there are places where you're getting bit-size mismatches between int types (ie, you still have a few chunks of code to update).

The trouble maker appears to be setFromZ. Here you have the integer type specified by Z, and the integer type specified by base::value_type. The two are not guaranteed to have the same number of bits, so the bit-shift and bit-mask code in that function are not working correctly when a bit mismatch occurs. And a mismatch is occurring for several instantiations of that template that occur in your ideone code, when uint32_t is the digit type (at least in my environment).

Edit: Confirmation that excessive right-shift is undefined according to standard: Reference: SO Post, see accepted answer, which quotes the standard.

(C++98 §5.8/1) states that: The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.

Community
  • 1
  • 1
WeirdlyCheezy
  • 704
  • 4
  • 4
  • Could I possibly ask you for more help? I changed `setFromZ`, but Im still getting the wrong output for the calculation. http://ideone.com/WjA0ZS . it seems to be multiplication erroring, but it works for `uint16_t` and `uint8_t` – calccrypto Oct 25 '12 at 19:20
  • 1
    I suspect something is causing the carry in the middle to not execute properly. I'll take a look once I get some time. Btw, what environment are you on? MSVC? You could also open another question if you wanted; now that the behavior is more reproducible, more people will be able to attack the problem. If you decide to do this, please feel free to post a link to the new question here. Either way, hopefully I'll get some time to look into it either tonight or tomorrow. – WeirdlyCheezy Oct 25 '12 at 20:17
  • Thanks! But I found the problem just now. I was not typecasting some stuff in `operator+`. Im using codeblocks, compiling with g++4.7 – calccrypto Oct 25 '12 at 20:19