Преобразование 74-битного целого числа в основание 31

Чтобы сгенерировать номер UFI я использую bitset размера 74. Чтобы выполнить шаг 2 поколения UFI, мне нужно преобразовать это число:

9 444 732 987 799 592 368 290
(10000000000000000000000000000101000001000001010000011101011111100010100010)

в:

DFSTTM62QN6DTV1

путем преобразования первого представления в базу 31 и получения эквивалентных символов из таблицы.

#define PAYLOAD_SIZE 74
// payload = binary of 9444732987799592368290
std::bitset<PAYLOAD_SIZE> bs_payload(payload);
/*
perform modulo 31 to obtain:
12(D), 14(F), 24(S), 25(T), 25, 19, 6, 2, 22, 20, 6, 12, 25, 27, 1
*/    

Есть ли способ выполнить преобразование моего набора битов без использования внешней библиотеки BigInteger?

Редактировать: наконец-то я создал класс BigInteger, даже если Cheers и чт. - Решение Alf работает как шарм


person thibsc    schedule 26.07.2018    source источник
comment
Некоторые компиляторы предоставляют (нестандартный) 128-битный целочисленный тип. Это __int128 в GCC. Есть ли он в вашем компиляторе? Если это так, вы можете преобразовать свой набор битов в целое число этого типа (побитно или с помощью reinterpret_casts, хотя в последнем случае будьте осторожны с прямым/обратным порядком байтов) и выполнить деление целых чисел напрямую.   -  person Ivan Smirnov    schedule 26.07.2018
comment
Я не понимаю, вы хотите применить только по модулю 31 один раз?   -  person Benjamin Barrois    schedule 26.07.2018
comment
@IvanSmirnov Я использую визуальный С++, а int128 не поддерживается.   -  person thibsc    schedule 26.07.2018
comment
Вы хотите выполнить операцию по модулю 31 числа?   -  person yadhu    schedule 26.07.2018
comment
@BenjaminBarrois, я хочу применить по модулю 31, пока значение не станет 0   -  person thibsc    schedule 26.07.2018
comment
На самом деле это означает преобразование в базу 31.   -  person Ivan Smirnov    schedule 26.07.2018
comment
@IvanSmirnov, да, извините, я редактирую, он конвертируется в базу 31   -  person thibsc    schedule 26.07.2018
comment
@ThibautB.: Visual C++ 2017 поддерживает __int128_t.   -  person Cheers and hth. - Alf    schedule 26.07.2018
comment
Вы могли бы просто попробовать. Кажется, это не задокументировано.   -  person Cheers and hth. - Alf    schedule 26.07.2018
comment
@Cheersandhth.-Alf, На виртуальной машине с 2017 или 2015 годом можно использовать __int128_t, но мой последний ответ неверен, мы не в 2015, а в 2010 году (я знаю, что это старо).   -  person thibsc    schedule 26.07.2018
comment
Итак, все, что выдают мои ассоциативные схемы, это то, что что-то в этом роде, например, по модулю с 2^n-1, обсуждалось в первом томе книги Кнута «Искусство компьютерного программирования». Также может быть китайская теорема об остатках, но я ничего не помню об этом. Это хорошие вещи, чтобы проверить в любом случае. Если это не поможет, то, похоже, вам придется либо реализовать общее деление целых чисел без знака. Или используйте библиотеку bignum (На ум приходит Boost multiprecision).   -  person Cheers and hth. - Alf    schedule 26.07.2018
comment
О, еще один. Вы можете вычислить значение двоичного числа по основанию 31. Это включает только умножение и сложение. Последняя цифра базы 31 — это ваш мод.   -  person Cheers and hth. - Alf    schedule 26.07.2018
comment
@Cheersandhth.-Альф, спасибо за помощь, у меня есть Искусство компьютерного программирования Vol.1 Второе издание, я изучаю эту книгу и вижу ваше второе предложение   -  person thibsc    schedule 26.07.2018
comment
@ТибоБ. Странно, __int128_t больше не работает с Visual C++ 2017, а значит, и раньше не работало. Хм! Во всяком случае, я приготовил некоторый код, опубликовав ответ сейчас.   -  person Cheers and hth. - Alf    schedule 26.07.2018
comment
@Cheersandhth.-Alf Я не знаю о VS 2017, но у него есть никогда не работал с MSVC   -  person phuclv    schedule 27.07.2018
comment
@Cheersandhth.-Alf MS подтвердила, что не будет поддерживать 128-бит в VS 2017   -  person phuclv    schedule 29.07.2018
comment
ваша реализация bigint хранит значение в виде строки, что очень неэффективно как для использования памяти, так и для производительности, поскольку одна цифра занимает один байт. Библиотеки с большими числами обычно используют основание 2⁶⁴ для максимальной эффективности на 64-разрядных компьютерах или основание 10¹⁹, если основными операциями являются ввод/ вывод   -  person phuclv    schedule 06.09.2018
comment
@phuclv, спасибо за этот совет, я изменю его, когда у меня будет время. Для наших текущих потребностей этого достаточно, но если нам нужна более высокая производительность, я держу ваше сообщение в углу;)   -  person thibsc    schedule 06.09.2018


Ответы (3)


Этот код, кажется, работает. Чтобы гарантировать результат, я думаю, вам нужно сделать дополнительное тестирование. Например. сначала с небольшими числами, где вы можете вычислить результат напрямую.

Редактировать: О, теперь я заметил, что вы опубликовали требуемые цифры результата, и они совпадают. Значит в целом неплохо, но еще не проверено на крайние случаи.

#include <assert.h>
#include <algorithm>            // std::reverse
#include <bitset>
#include <vector>
#include <iostream>
using namespace std;

template< class Type > using ref_ = Type&;

namespace base31
{
    void mul2( ref_<vector<int>> digits )
    {
        int carry = 0;
        for( ref_<int> d : digits )
        {
            const int local_sum = 2*d + carry;
            d = local_sum % 31;
            carry = local_sum / 31;
        }
        if( carry != 0 )
        {
            digits.push_back( carry );
        }
    }

    void add1( ref_<vector<int>> digits )
    {
        int carry = 1;
        for( ref_<int> d : digits )
        {
            const int local_sum = d + carry;
            d = local_sum % 31;
            carry = local_sum / 31;
        }
        if( carry != 0 )
        {
            digits.push_back( carry );
        }
    }

    void divmod2( ref_<vector<int>> digits, ref_<int> mod )
    {
        int carry = 0;
        for( int i = int( digits.size() ) - 1; i >= 0; --i )
        {
            ref_<int> d = digits[i];
            const int divisor = d + 31*carry;
            carry = divisor % 2;
            d = divisor/2;
        }
        mod = carry;
        if( digits.size() > 0 and digits.back() == 0 )
        {
            digits.resize( digits.size() - 1 );
        }
    }
}


int main() {
    bitset<74> bits(
        "10000000000000000000000000000101000001000001010000011101011111100010100010"
        );
    vector<int> reversed_binary;
    for( const char ch : bits.to_string() ) { reversed_binary.push_back( ch - '0' ); }

    vector<int> base31;
    for( const int bit : reversed_binary )
    {
        base31::mul2( base31 );
        if( bit != 0 )
        {
            base31::add1( base31 );
        }
    }

    { // Check the conversion to base31 by converting back to base 2, roundtrip:
        vector<int> temp31 = base31;
        int mod;
        vector<int> base2;
        while( temp31.size() > 0 )
        {
            base31::divmod2( temp31, mod );
            base2.push_back( mod );
        }
        reverse( base2.begin(), base2.end() );
        cout << "Original     : " << bits.to_string() << endl;
        cout << "Reconstituted: ";
        string s;
        for( const int bit : base2 ) { s += bit + '0'; cout << bit; };  cout << endl;
        assert( s == bits.to_string() );
    }

    cout << "Base 31 digits (msd to lsd order): ";
    for( int i = int( base31.size() ) - 1; i >= 0; --i )
    {
        cout << base31[i] << ' ';
    }
    cout << endl;

    cout << "Mod 31 = " << base31[0] << endl;
}

Результаты с MinGW g++:

Original     : 10000000000000000000000000000101000001000001010000011101011111100010100010
Reconstituted: 10000000000000000000000000000101000001000001010000011101011111100010100010
Base 31 digits (msd to lsd order): 12 14 24 25 25 19 6 2 22 20 6 12 25 27 1
Mod 31 = 1
person Cheers and hth. - Alf    schedule 26.07.2018
comment
О, это похоже на красивое решение, но я сейчас не в офисе, я протестирую его и подтвержу вам завтра для другого случая. - person thibsc; 26.07.2018
comment
В чем причина использования ref_ вместо &? - person Ivan Smirnov; 26.07.2018
comment
Просто мое предпочтительное обозначение. Он поддерживает постоянный префикс const. - person Cheers and hth. - Alf; 26.07.2018
comment
@Cheersandhth.-Альф, по малым результатам вроде бы всегда так, но у меня есть сомнения по поводу больших целых чисел, но это очень сложно определить, поэтому я продолжаю тестировать - person thibsc; 27.07.2018
comment
@Cheersandhth.-Alf, я сравниваю с java-программой с классом BigInteger, и этот код всегда дает одинаковые результаты, он работает как шарм :) - person thibsc; 30.07.2018

Чтобы получить число по модулю 31, вам просто нужно суммировать цифры в основание 32, точно так же, как вы вычисляете по модулю 3 и 9 десятичного числа

unsigned mod31(std::bitset<74> b) {
    unsigned mod = 0;
    while (!b.none()) {
        mod += (b & std::bitset<74>(0x1F)).to_ulong();
        b >>= 5;
    }
    while (mod > 31)
        mod = (mod >> 5) + (mod & 0x1F);
    return mod;   
}

Вы можете ускорить расчет по модулю, запустив дополнения параллельно, например как это сделано здесь. Аналогичный метод можно использовать для вычисления по модулю 3, 5, 7, 15... и 231 - 1.

Однако, поскольку вопрос на самом деле касается базового преобразования, а не модуля, как сказано в заголовке, для этой цели вам нужно выполнить реальное деление. Обратите внимание, что 1/b равно 0.(1) по основанию b + 1, мы имеем

1/31 = 0,000010000100001000010000100001...32 = 0.(00001)32

и тогда N/31 можно рассчитать так

N/31 = N×2-5 + N×2-10 + N×2-15 + ...

uint128_t result = 0;
while (x)
{
    x >>= 5;
    result += x;
}

Поскольку и по модулю, и по делению используется сдвиг на 5, вы также можете выполнить их вместе в одном цикле.

Однако сложная часть здесь заключается в том, как правильно округлить частное. Вышеупомянутый метод будет работать для большинства значений, за исключением некоторых между кратным 31 и следующей степенью 2. Я нашел способ исправить результат для значений до нескольких тысяч, но еще не нашел общий способ для всех значений

Вы можете видеть, что тот же метод сдвига и добавления используется для деления на 10 и на 3. В знаменитом Hacker's Delight есть еще примеры с правильным округлением. У меня не было достаточно времени, чтобы прочитать книгу, чтобы понять, как они реализуют часть коррекции результатов, поэтому, возможно, я вернусь к этому позже. Если у кого-то есть идеи, как это сделать, буду признателен.

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

uint128_t result = 0;
const unsigned num_fraction = 125 - 75 // 125 and 75 are the nearest multiple of 5
// or maybe 128 - 74 will also work
uint128_t x = UFI_Number << num_fraction; 

while (x)
{
    x >>= 5;
    result += x;
}
// shift the result back and add the fractional bit to round
result = (result >> num_fraction) + ((result >> (num_fraction - 1)) & 1)

Обратите внимание, что ваш результат выше неверен. Я подтвердил, что результат CEOPPJ62MK6CPR1 из обоих ответа Янива Шакеда и Wolfra разные символы для цифр

person phuclv    schedule 26.07.2018
comment
Я проверил этот код, возможно, это потому, что это конец рабочего дня, но я не могу получить ожидаемые значения [12, 14, 24, 25, 25, 19, 6, 2, 22, 20, 6, 12, 25, 27, 1] - person thibsc; 26.07.2018
comment
Для решения проблемы тоже нужен div31, не так ли? - person geza; 26.07.2018
comment
хорошо, я неправильно понял вопрос. Речь идет об базовом преобразовании, а не по модулю, поэтому вам определенно нужно деление - person phuclv; 27.07.2018
comment
@phuclv, спасибо за ваше время, преобразование 31 базы дает CEOPPJ62MK6CPR1, но не в случае генерации номера UFI, потому что они удаляют неоднозначные символы, такие как [I, B...] поэтому мне просто нужно значение преобразования int, чтобы получить правильный символ base31 в соответствии с правилами UFI. - person thibsc; 27.07.2018

Я не компилировал псевдокод, но вы можете получить представление о том, как преобразовать число:

// Array for conversion of value to base-31 characters:
char base31Characters[] = 
{
    '0',
    '1',
    '2',
    ...
    'X',
    'Y'
};

void printUFINumber(__int128_t number)
{
    string result = "";
    while (number != 0)
    {
        var mod = number % 31;
        result = base31Characters[mod] + result;
        number = number / 31;
    }
    cout << number;
}
person Yaniv Shaked    schedule 26.07.2018
comment
Спасибо за преобразование в 31 базу, но моя проблема в том, что я не могу использовать тип __int128_t - person thibsc; 26.07.2018
comment
Я не уверен, хотите ли вы двигаться в этом направлении, но существуют 128-битные реализации, когда платформа/компилятор их не поддерживает: github.com/calccrypto/uint128_t - person Yaniv Shaked; 26.07.2018
comment
Я буду использовать это решение только в крайнем случае, я сохраняю эту ссылку;) - person thibsc; 26.07.2018