Первое, что вам нужно сделать, это иметь работающий тестовый движок. Это безмозглый, простой для понимания арифметический механизм произвольной точности.
Цель этого двигателя несколько раз. Во-первых, это делает преобразование строк в целые числа произвольной точности очень простым. Во-вторых, это средство для проверки ваших более поздних улучшенных двигателей. Даже если это действительно медленно, вы будете более уверены, что это правильно (и наличие двух независимых реализаций означает, что ошибки в одном случае могут быть обнаружены в другом, даже если вы не уверены ни в том, ни в другом).
Предполагается, что short
составляет не менее 16 бит, а char
- не менее 8 (используйте фактические типы стиля int_8
, если ваш компилятор их поддерживает)
short Add(unsigned char left, unsigned char right, unsigned char extra=0) { return unsigned short(left)+unsigned short(right)+unsigned short(extra); }
unsigned short Multiply(unsigned char left, unsigned char right) { return unsigned short(left)*unsigned short(right); }
std::pair<unsigned char,unsigned char> CarryCalc(unsigned short input) {
std::pair<unsigned char,unsigned char> retval;
retval.first = input & (1<<8-1);
retval.second = input>>8;
return retval;
}
struct BigNum {
std::vector<char> base256;
BigNum& operator+=( BigNum const& right ) {
if (right.base256.size() > base256.size())
base256.resize(right.base256.size());
auto lhs = base256.begin();
auto rhs = right.base256.begin();
char carry = 0;
for(; rhs != right.base256.end(); ++rhs, ++lhs) {
auto result = CarryCalc( Add( *lhs, *rhs, carry ) );
*lhs = result.first;
carry = result.second;
}
while( carry && lhs != base256.end() ) {
auto result = CarryCalc( Add( *lhs, 0, carry ) );
*lhs = result.first;
carry = result.second;
}
if (carry)
base256.push_back(carry);
return *this;
}
BigNum& scaleByChar( unsigned char right ) {
char carry = 0;
for(auto lhs = base256.begin(); lhs != base256.end(); ++lhs) {
unsigned short product = Multiply( *lhs, right );
product += carry;
auto result = CarryCalc( product );
*lhs = result.first;
carry = result.second;
}
if (carry)
base256.push_back(carry);
return *this;
}
BigNum& shiftRightBy8BitsTimes( unsigned int x ) {
if (x > base256.size()) {
base256.clear();
return *this;
}
base256.erase( base256.begin(), base256.begin()+x) )
return *this;
}
// very slow, O(x * n) -- should be O(n) at worst
BigNum& shiftLeftBy8BitsTimes( unsigned int x ) {
while( x != 0 ) {
base256.insert( base256.begin(), 0 );
--x;
}
return *this;
}
// very slow, like O(n^3) or worse (should be O(n^2) at worst, fix shiftLeft)
BigNum& operator*=( BigNum const& right ) {
unsigned int digit = 0;
BigNum retval;
while (digit < right.base256.size()) {
BigNum tmp = *this;
tmp.shiftLeftBy8BitsTimes( digit );
tmp.scaleByChar( right.base256[digit] );
retval += tmp;
++digit;
}
*this = retval;
return *this;
}
};
который представляет собой быстрый и грязный целочисленный тип произвольной точности (еще даже не скомпилированный) с ужасной производительностью. Протестируйте что-то вроде приведенного выше, убедите себя, что это надежно, а затем начните с этого.
Большая часть вашего кода может использовать фактический рассматриваемый класс BigNum в качестве аргумента шаблона, поэтому вы можете выполнить один и тот же алгоритм с двумя разными реализациями и сравнить результаты в целях тестирования.
О, и еще один совет — напишите класс шаблона, который «улучшает» базовую библиотеку произвольной точности через CRTP. Цель состоит в том, чтобы написать только функции *=
, +=
, unary -
и, возможно, /=
и некоторые функции shift_helper
и compare_helper
, а остальные ваши методы будут автоматически написаны для вас шаблоном. Размещение шаблона в одном месте упрощает поддержку более чем одной версии вашего класса BigNum
, а наличие более одной версии очень важно для целей тестирования.
person
Yakk - Adam Nevraumont
schedule
10.12.2012