Самый быстрый способ преобразования двоичного кода в десятичный?

У меня есть четыре беззнаковых 32-битных целых числа, представляющих беззнаковое 128-битное целое число, в маленьком концевом порядке:

typedef struct {
    unsigned int part[4];
} bigint_t;

Я хотел бы преобразовать это число в его десятичное строковое представление и вывести его в файл.

Сейчас я использую функцию bigint_divmod10 для деления числа на 10, отслеживая остаток. Я вызываю эту функцию несколько раз, выводя остаток в виде цифры, пока номер не станет нулевым. Это довольно медленно. Это самый быстрый способ сделать это? Если да, есть ли умный способ реализовать эту функцию, которую я не вижу? Я пробовал смотреть на GMP get_str.c, но я нахожу его довольно непроницаемым.

EDIT: здесь самый быстрый код, который я смог найти для функции divmod10:

static unsigned uint128_divmod10(uint128 *value)
{
    unsigned int a = value->word[3];
    unsigned int b = value->word[2];
    unsigned int c = value->word[1];
    unsigned int d = value->word[0];

    unsigned int diva = a / 5;
    unsigned int divb = b / 5;
    unsigned int divc = c / 5;
    unsigned int divd = d / 5;

    value->word[3] = diva;
    value->word[2] = divb;
    value->word[1] = divc;
    value->word[0] = divd;

    unsigned int moda = a - diva*5;
    unsigned int modb = b - divb*5;
    unsigned int modc = c - divc*5;
    unsigned int modd = d - divd*5;

    unsigned int mod = 0;
    mod += moda;
    unsigned int carryb = mod*858993459;
    mod += modb;
    if (mod >= 5) {
        mod -= 5;
        carryb++;
    }
    unsigned int carryc = mod*858993459;
    mod += modc;
    if (mod >= 5) {
        mod -= 5;
        carryc++;
    }
    unsigned int carryd = mod*858993459;
    mod += modd;
    if (mod >= 5) {
        mod -= 5;
        carryd++;
    }

    uint128_add(value, carryd, 0);
    uint128_add(value, carryc, 1);
    uint128_add(value, carryb, 2);

    if (value->word[0] & 1) {
        mod += 5;
    }
    uint128_shift(value, -1);
    return mod;
}

где функция добавления определяется как:

static void uint128_add(uint128 *value, unsigned int k, unsigned int pos)
{
    unsigned int a = value->word[pos];
    value->word[pos] += k;
    if (value->word[pos] < a) {
        // overflow
        for (int i=pos+1; i<4; i++) {
            value->word[i]++;
            if (value->word[i]) {
                break;
            }
        }
    }
}
7
задан ianh 06 нояб. '09 в 10:33
источник поделиться
6 ответов

Это зависит от того, что вы делаете с цифрами. Вы можете компенсировать небольшую потерю эффективности использования пространства и небольшую потерю эффективности многоточечной арифметики в обмен на очень эффективное преобразование в десятичный и десятичный. Ключ состоит в том, чтобы выполнить арифметику multiprecision с базой, которая имеет мощность 10, а не мощность 2.

Например, вы можете использовать базу 10 000, где вы упаковываете одну цифру в 16-битное слово, и вы делаете свою арифметику на цифрах в 32-битных целых числах. (Если вы на 64-битной машине, вы можете удвоить эту базу и сделать базовую 1 000 000 000.) Этот вид кода относительно эффективен с временным интервалом, хотя и не так быстро, как использование собственной силы в два, потому что вы не можете воспользоваться преимуществами бит переноса на аппаратном обеспечении. И вы не можете представлять столько целых чисел в одном и том же количестве бит. Но это свист при конвертировании в и из десятичного числа, потому что вы можете преобразовать отдельные цифры без какого-либо длительного разделения.

Если вам нужно представить полный диапазон чисел от нуля до ((1 << 128) - 1), вы все равно можете это сделать, но добавьте дополнительную цифру, чтобы ваши номера были больше.

Если вам действительно нужно дополнительное пространство/скорость (возможно, вы делаете много криптографических 128-битных вычислений), то метод одновременного div/mod на 10 является самым быстрым методом, который я знаю. Единственный трюк в том, что если маленькие целые числа являются общими, вы можете справиться с ними специально. (То есть, если три наиболее значимых 32-битных слова равны нулю, просто используйте собственное деление для преобразования.)

Есть ли умный способ реализовать эту функцию, которую я не вижу?

Дэйв Хэнсон C Интерфейсы и реализации содержит длинную главу о многоточечной арифметике. Разделение большого числа на одну цифру - это особый случай, который имеет эту эффективную реализацию:

int XP_quotient(int n, T z, T x, int y) {
    int i;
    unsigned carry = 0;
    for (i = n - 1; i >= 0; i--) {
        carry = carry*BASE + x[i];
        z[i] = carry/y;
        carry %= y;
    }
    return carry;
}

Для полного понимания это действительно помогает иметь книгу, но исходный код все еще намного легче понять, чем источник GNU код. И вы можете легко адаптировать его для использования базы 10 000 (в настоящее время она использует базу 256).

Сводка: если узким местом вашей производительности является преобразование в десятичное число, выполните арифметику с несколькими значениями с базой, которая имеет мощность 10. Если размер вашего родного слова вашего компьютера равен 32, и вы используете код C используйте 10 000 фунтов стерлингов в 16-разрядном слове.

4
ответ дан Norman Ramsey 08 нояб. '09 в 1:10
источник поделиться

Если ваши значения в основном меньше ULLONG_MAX (18446744073709551615), я попытаюсь использовать для них sprintf(buf,"%llu",ullong_val). Я уверен, что это довольно хорошо оптимизировано в стандартной библиотеке, но разбор формата займет несколько циклов.

В противном случае я бы создал функцию bigint_divmod1000000000 (или лучше имя mod10to9) и использовал ее. Это потребует в 9 раз меньше делений, чем bigint_divmod10.

3
ответ дан Tometzky 06 нояб. '09 в 11:00
источник поделиться

Таблица поиска 8 бит. Вы можете иметь 4 таблицы поиска из 256 номеров. Первый из 0-256 для байтов LSB, вторая таблица - первая таблица, умноженная на 256 и т.д.

SO, когда вам нужно, чтобы цифры суммировали номера из таблицы поиска. Когда вы добавляете, вы можете добавить как bunary и пройти через один проход по каждому байту, чтобы исправить owerflows.

Пример номер 0x12345678 В первой таблице поиска находится под addres (0x78 = 120) поэтому 0x010200 - это первый номер во второй таблице под (0x56 = 87) равен 0x0202000106 (0x56 в dec 22016) в третьей таблице у вас будет 0x03040007080702 и под последней буквой в 0x12 у вас есть 0x030001090809080808 (это не соответствует 32-разрядной арифметике, но вы знаете все)

Затем суммируем эти числа (как двоичные бамперы) и пройдите один проход, байт за байтом для переполнения код для цикла - это что-то вроде

s=carry+val[i];
val[i]=val[i]&10
carry=s/10; 
//you can put last two operations in table

Если мы подсчитаем необходимые для этого операции.

1. (просмотр в таблицах и добавление) 4 таблицы поиска. 16 дополнений (имейте в виду, что когда вам не нужно носить с собой owerflow, потому что они не могут быть) 2. один проход на каждом шаге 3 операнта 16 шагов для прохождения.

пассивная верхняя граница 6 * 16 = 100 операций.

EDIT:

Вот код С++ и на 30% быстрее, чем наивная реализация.

#include <iostream>
#include <stdint.h>
#include <array>

static uint64_t lu[4][256];

constexpr uint64_t lookup_value(uint64_t n) {
  uint64_t r = 0;
  uint64_t t = 1;
  while (n) {
    uint64_t rem = n % 10;
    n /= 10;
    r += rem * t;
    t *= 256;
  }
  return r;
}

void make_lu() {
  uint64_t step = 1;
  for (int j = 0; j < 4; ++j) {
    uint64_t n = 0;
    for (int i = 0; i < 256; ++i) {
      lu[j][i] = lookup_value(n);
      n += step;
    }
    step *= 256;
  }
}

struct DivMod {
  uint8_t div;
  uint8_t rem;
};

static DivMod dm[256];

void make_dm() {
  for (int i = 0; i < 256; ++i) {
    dm[i].div = i / 10;
    dm[i].rem = i % 10;
  }
}

void init() {
  make_lu();
  make_dm();
}

uint64_t b2d(uint64_t n) {
  uint64_t r = 0;
  for (int i = 0; i < 4; ++i) {
    r += lu[i][(n >> (i * 8)) & 0xff];
  }
  uint64_t r2 = 0;
  uint64_t of = 0;
  for (int i = 0; i < 8; ++i) {
    uint64_t v = ((r >> (i * 8)) & 0xff) + of;
    DivMod &x = dm[v];
    of = x.div;
    r2 += uint64_t(x.rem) << (i * 8);
  }
  return r2;
}

int main() {
  init();
  uint64_t n;
  std::cin >> n;
  std::cout << std::hex << b2d(n) << "\n";
  return 0;
}
1
ответ дан Luka Rahne 06 нояб. '09 в 11:36
источник поделиться

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

0
ответ дан ianh 07 нояб. '09 в 10:40
источник поделиться

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

#include <iostream>
#include <cmath>
using namespace std;

#define MathBintodec(arr,len)({int dec=0;int ci_;for(ci_=len;ci_--;)dec+=arr[ci_]*pow(2,len-ci_-1);dec;})

int main(){
    int r[]={1,0,0,1,0,0};
    cout<<MathBintodec(r,6)<<endl;
}

Вывод: 36

-1
ответ дан user2170324 03 июля '13 в 6:43
источник поделиться

Самое непосредственное ускорение будет происходить при встраивании преобразования, а не в вызове функций; это может быть так же просто, как маркировка bigint_divmod10() inline, или оптимизация с помощью профиля, предложенная вашим компилятором.

-1
ответ дан Will 06 нояб. '09 в 10:54
источник поделиться

Другие вопросы по меткам