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?
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.