Как преобразовать строку в целое число произвольной длины

Я работаю над проектом по реализации арифметики с высокой точностью на С++. Я как бы упал на первом препятствии. Я пытаюсь преобразовать c-строку в двоичное представление целого числа, которое, вероятно, будет содержать больше битов, чем может содержать int (теоретически это может быть произвольное количество битов). По сути, я хочу создать массив длинных чисел, который будет содержать двоичное представление числа, содержащегося в строке, с индексом 0, являющимся наименее значимой «конечностью» числа. Я предполагаю, что число находится в базе десять.

Я уже изучал использование кода из GMP, но он излишне сложен для моих нужд и содержит огромное количество кода, зависящего от платформы.

Любая помощь будет здорово! Если вам нужна дополнительная информация, дайте мне знать.


person Chris McCabe    schedule 10.12.2012    source источник
comment
Я думаю, вы сначала пытаетесь преодолеть не то препятствие. Реализуйте арифметику, а затем используйте ее для преобразования строки с основанием 10 в ваше внутреннее представление. Это довольно очевидная последовательность умножений и сложений, по крайней мере, пока вы не начнете оптимизировать.   -  person Steve Jessop    schedule 10.12.2012
comment
@SteveJessop Спасибо за комментарий, причина, по которой я не сделал этого, заключается в том, что операции будут выполняться с ускорением графического процессора, что создает целый ряд проблем. Я знаю, что сначала можно реализовать преобразование, как это делает GMP.   -  person Chris McCabe    schedule 10.12.2012
comment
Вы заново изобретаете колесо. Используйте GMP. Весь код, зависящий от платформы GMP, используется только в том случае, если он применим к вашей конфигурации.   -  person brian beuning    schedule 10.12.2012
comment
@brianbeuning: используйте GMP - в зависимости от среды. К сожалению, GMP не может восстановиться после нехватки памяти. Это может быть хорошо в средах, где программы никогда не восстанавливаются после нехватки памяти, и это может даже быть значительной оптимизацией. Однако это делает интерфейс GMP непригодным для использования в определенных средах.   -  person Steve Jessop    schedule 10.12.2012
comment
@brianbeuning Я думаю, что вы немного упустили суть этого, я создаю библиотеку мультиточности с ускорением на графическом процессоре, она должна работать примерно в 10 раз лучше, чем GMP, когда она будет завершена.   -  person Chris McCabe    schedule 12.12.2012


Ответы (2)


Как сказал @SteveJessop

class Number {
public:
    Number();
    void FromString( const char * );
    void operator *= ( int );
    void operator += ( int );
    void operator = ( int );
}

Number::FromString( const char * string )
{
    *this = 0;
    while( *string != '\0' ) {
        *this *= 10;
        *this += *string - '0';
        string++;
    }
}
person brian beuning    schedule 10.12.2012
comment
Я пришел к тому же выводу, что на данный момент это самый простой подход, я знаю, что есть более эффективное решение, и я опубликую его здесь, если/когда найду его. - person Chris McCabe; 11.12.2012
comment
Один из способов сделать это быстрее - преобразовать 9 символов в целое число, а затем сложить целое число в число. Как только останется менее 9 цифр, переключитесь на указанный выше код. - person brian beuning; 12.12.2012

Первое, что вам нужно сделать, это иметь работающий тестовый движок. Это безмозглый, простой для понимания арифметический механизм произвольной точности.

Цель этого двигателя несколько раз. Во-первых, это делает преобразование строк в целые числа произвольной точности очень простым. Во-вторых, это средство для проверки ваших более поздних улучшенных двигателей. Даже если это действительно медленно, вы будете более уверены, что это правильно (и наличие двух независимых реализаций означает, что ошибки в одном случае могут быть обнаружены в другом, даже если вы не уверены ни в том, ни в другом).

Предполагается, что 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