我试图转换我的arbitrary precision integer类,以便能够使用不仅仅是每位8位的数字。我偶然发现了一个奇怪的问题:我可以使用uint16_t
对于我的基本数字类型,但不是uint32_t
。我的代码将返回错误的结果。我用来查找出错的示例是0x1111111111111111 * 0x1111111111111111
,应该是0x123456789abcdf00fedcba987654321
。但是,我得到0x123456789abcdf0fedcba987654321
。
我认为我已经更改了所有硬编码类型,因此更改基本数字类型无关紧要,但显然不是。
以下是相关代码:
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;
}
我错过了哪些可能导致错误计算的明显事项?我在一次爆发中盯着这段代码看了一会儿。
完整修改后的代码为here。
编辑:我已经在ideone上测试了代码,并且它正在为此计算返回正确的值,但我的计算机仍然没有。对此有什么好的解释吗?
答案 0 :(得分:2)
问题似乎是由于意外的过度右移。你提到你的ideone帖子没有重现问题。在测试时,我注意到在你的ideone帖子中,数字类型实际上仍设置为16位。我把它改成32位(以反映SO代码片段)并尝试在gcc下编译的那一刻,我收到了一些关于过度右移的警告。运行该程序导致无限循环,结合右移警告以及您在平台上获得不同行为的事实强烈建议发生未定义的行为。
更具体地说,在某些地方你会遇到int类型之间的位大小不匹配(即,你仍然有一些代码要更新)。
麻烦制造者似乎是setFromZ。这里有Z指定的整数类型,以及base :: value_type指定的整数类型。这两个不能保证具有相同的位数,因此当发生位不匹配时,该函数中的位移和位掩码代码无法正常工作。当uint32_t是数字类型时(至少在我的环境中),当你的ideone代码中出现的那个模板的几个实例化发生了不匹配。
编辑:确认根据标准未定义过度右移:参考:SO Post,参见接受的答案,引用标准。
(C ++98§5.8/ 1)指出: 行为是 如果右操作数为负数或大于或等于,则为undefined 到升级的左操作数的位数。