Можно ли получить исходное значение числа после нескольких умножений **с переполнением**?

Резюме: Есть ли способ сделать это? Вот что я имею в виду: предположим, у меня есть число unsigned int. Затем я умножаю его несколько раз (и происходит переполнение, что и ожидается). Тогда можно ли «вернуть» исходное значение обратно?


Подробно:

Все дело в катящемся хеше Рабина-Карпа. Что мне нужно сделать, так это: у меня есть хэш длинной строки, например: «abcd». Затем у меня есть хэш для более короткой подстроки, например «cd». Как рассчитать хэш «ab» с O (1), используя два заданных хэша?

Что у меня есть сейчас в качестве алгоритма:

  • вычесть хэш "cd" из хеша "abcd" (удалить последние элементы из полинома)
  • разделите хэш «abcd» на p ^ len( "cd" ), где p — это основание (простое число).

Так что это:

a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0 – abcd

c * p ^ 1 + d * p ^ 0 - кд

ab получает:

( 
  ( a * p ^ 3 + b * p ^ 2 + c * p ^ 1 + d * p ^ 0 ) -
  ( c * p ^ 1 + d * p ^ 0 ) 
)
/ ( p ^ 2 )
= a * p ^ 1 + b * p ^ 0

И это работает, если у меня нет переполнения (если p мало). Но если нет - не работает.

Есть какой-то трюк или что-то в этом роде?

P.S. Тег c++ связан с переполнением числа, поскольку он специфичен (и отличается от python, схемы или чего-либо)


person Kiril Kirov    schedule 07.05.2011    source источник
comment
Для p = 2 это невозможно. Для всех остальных простых чисел p возможно...   -  person Sven Marnach    schedule 07.05.2011
comment
@Sven Marnach - ну как? Я не могу вычитать последнюю букву, потом делить по основанию (p), и снова вычитать предпоследнюю букву и снова делить на p и т.д., так как не знаю строк, а только их хеш..   -  person Kiril Kirov    schedule 07.05.2011
comment
@Sven Marnach - также, что возможно? Вернуть число или вычислить хэш? Если второе, я думаю, мне нужно будет принять ответ cnicutar и задать новый вопрос о хешировании?   -  person Kiril Kirov    schedule 07.05.2011


Ответы (6)


Не знаю о части переполнения, но есть способ вернуть исходное значение.

Китайская теорема об остатках очень помогает. Давайте позвоним h = abcd - cd. G — это значение, h, без переполнения, G = h + k*2^32, при условии, что переполнение просто выполняет %2^32. И таким образом ab = G / p^2.

G = h (mod 2^32)
G = 0 (mod p^2)

Если p^2 и 2^32 взаимно просты. Эта страница, посвященная китайской теореме об остатках, дает нам

G = h * b * p^2 (mod 2^32 * p^2)

Где b — модульный мультипликатив, обратный p^2 по модулю 2^32, b * p^2 = 1 (mod 2^32). После того, как вы вычислите G, просто разделите на p^2, чтобы найти ab.

Надеюсь, я не допустил ошибок...

person Ishtar    schedule 07.05.2011

Расширенный алгоритм Евклида является хорошим решением для этого, но он слишком сложен и трудно реализуем. Есть лучше.


И есть еще один способ сделать это (спасибо моему другу (:)

В википедии есть хорошая статья - модульное мультипликативное обратное с использованием теоремы Эйлера в случае, когда m и a взаимно просты:

Теорема Эйлера для взаимно простого числа и модуля

где φ(m) — это функция totient Эйлера.

В моем случае m (по модулю) - это размер типа хэша - 2^32, 2^64 и т. д. (64 бит в моем случае).
Это означает, что нам нужно найти только значение φ(m). Но подумайте об этом - m == 2 ^ 64 таким образом, это дает нам гарантию, что m будет взаимно простым со всеми нечетными числами и НЕ будет взаимно простым с любым четным числом. Итак, что нам нужно сделать, это получить количество всех значений и разделить их на 2.

Кроме того, мы знаем, что m не будет подписано, иначе у нас возникнут проблемы. Чем это дает нам возможность сделать это:

hash_t x = -1;
x /= 2;
hash_t a_reverse = fast_pow( a, x );

Ну, о 64-битных числах, x действительно большое число (19 цифр: 9 223 372 036 854 775 807), но fast_pow очень быстрое, и мы могли бы кэшировать обратное число, если нам нужно более одного запроса.

fast_pow — известный алгоритм:

hash_t fast_pow( hash_t source, hash_t pow )
{
    if( 0 == pow )
    {
        return 1;
    }

    if( 0 != pow % 2 )
    {
        return source * fast_pow( source, pow - 1 );
    }
    else
    {
        return fast_pow( source * source, pow / 2  );    
    }

}

Дополнение: например:

    hash_t base = 2305843009213693951;  // 9th mersenne prime
    hash_t x = 1234567890987654321;

    x *= fast_pow( base, 123456789 );   // x * ( base ^ 123456789 )

    hash_t y = -1;
    y /= 2;
    hash_t base_reverse = fast_pow( base, y );

    x *= fast_pow( base_reverse, 123456789 );   // x * ( base_reverse ^ 123456789 )
    assert( x == 1234567890987654321 ) ;

работает идеально и очень быстро.

person Kiril Kirov    schedule 08.05.2011

Вы должны использовать целые числа без знака, чтобы получить определенное поведение переполнения (по модулю 2 ^ N). Переполнение целого числа со знаком не определено.

Кроме того, вместо деления вы должны умножить на мультипликативную обратную величину p по модулю соответствующего значения. Например, если p=3 и ваши хеш-значения 8-битные, умножьте на 171, потому что 171*3=513=2*256+1. Мультипликативная обратная существует, если p и значение по модулю взаимно просты.

person jilles    schedule 07.05.2011
comment
Это unsigned int, я только что отредактировал вопрос. Хм, это звучит разумно, но, как я знаю, найти такой обратный элемент далеко не тривиальная задача и замедлит работу.. +1 за идею. Я подумаю об этом. - person Kiril Kirov; 07.05.2011

Просто частичный побочный ответ здесь: я считаю, что нет строго необходимо использовать целые числа без знака. Вы можете использовать дополнение.

Но обратите внимание, что это будет иметь отдельное представление для -0 и +0, и что вам, вероятно, придется вручную кодировать арифметические операции по пути.

Некоторые инструкции процессора не зависят от целочисленного представления, но не все.

person sehe    schedule 07.05.2011

У вас есть * b = c mod 2 ^ 32 (или что-то еще, в зависимости от того, как вы делаете свой хэш). Если бы вы могли найти d такое, что b * d = 1 mod 2 ^ 32 (или любой другой мод), вы могли бы вычислить a * b * d = a и получить a. Если gcd(b, mod 2^32) = 1, вы можете использовать http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm, чтобы найти x и y, такие что b * x + 2^32 * y = 1, или b * x = 1 - y * 2^32, или b * x = 1 mod 2^ 32, значит, х — это число, на которое нужно умножить.

person mcdowella    schedule 08.05.2011

Таким образом, переполнение на самом деле просто ваш компилятор хорошо к вам относится; стандарт C/++ фактически предполагает, что переполнение является неопределенным поведением. Итак, как только вы переполнились, вы фактически ничего не можете сделать, потому что ваша программа перестает быть детерминированной.

Возможно, вам придется переосмыслить алгоритм или добавить операции по модулю/вычитания, чтобы исправить ваш алгоритм.

person Ben Stott    schedule 07.05.2011
comment
Значения без знака не переполняются. Ваш ответ относится только к арифметике со знаком. - person R.. GitHub STOP HELPING ICE; 07.05.2011
comment
Это не определено стандартом, но определено архитектурой. В x86 инструкция add имеет детерминированное поведение, известное как переполнение. - person Travis Gockel; 07.05.2011
comment
@R: значения без знака могут переполняться. uint8_t x = 255; x += 1; Значение x будет 0 -- переполнение. - person Travis Gockel; 07.05.2011
comment
@Travis: нет, это перенос, а не переполнение. а для беззнаковых типов это четко определенное поведение, и стандарт точно описывает, как это делается, а именно с помощью арифметического модуля, соответствующей мощности 2. - person Jens Gustedt; 07.05.2011
comment
Да, когда я опубликовал свой ответ, разъяснение относительно беззнаковости еще не было сделано. Кроме того, определение архитектуры ничего не значит; в общем случае это не сработает, потому что не вся архитектура действует одинаково! - person Ben Stott; 08.05.2011
comment
Извините, упаковка. Поскольку перенос целочисленных значений без знака четко определен, а целые числа со знаком реализованы как дополнение 2 к unsigned int, не будет ли перенос целочисленных значений со знаком по-прежнему четко определенным (возможно, не напрямую, а в результате того, что поведение является комбинацией двух четко определенных те)? - person Travis Gockel; 08.05.2011
comment
@Ben Stott: перенос целых чисел без знака определен в стандарте C, он не зависит от архитектуры. - person ninjalj; 08.05.2011