"Скасування" цілої огортання


20

Я зіткнувся з цікавою теоретичною проблемою кілька років тому. Я ніколи не знайшов рішення, і воно продовжує переслідувати мене, коли я сплю.

Припустимо, у вас є (C #) додаток, яке містить деяке число в int, зване x. (Значення x не фіксовано). Під час запуску програми х множать на 33 і записують у файл.

Основний вихідний код виглядає так:

int x = getSomeInt();
x = x * 33;
file.WriteLine(x); // Writes x to the file in decimal format

Через кілька років ви виявите, що потрібні початкові значення X назад. Деякі обчислення прості: просто розділіть число у файлі на 33. Однак в інших випадках X досить великий, що множення спричинило переповнення цілого числа. Згідно з документами , C # обрізає біти високого порядку, поки число не стане меншим за int.MaxValue. Чи можливо в такому випадку:

  1. Відновити X або
  2. Відновити список можливих значень для X?

Мені здається (хоча моя логіка, безумовно, може бути хибною), що один чи обидва повинні бути можливими, оскільки спрощений випадок додавання працює (По суті, якщо ви додасте 10 до X і він завершить, ви можете відняти 10 і знову повернутись з X ) і множення - це просто повторне додавання. Також допомагає (я вважаю) той факт, що X множиться на однакове значення у всіх випадках - константа 33.

Це вже багато років танцює навколо мого черепа. Це станеться мені, я проведу деякий час, намагаючись продумати його, а потім забуду про це на кілька місяців. Я втомився переслідувати цю проблему! Хтось може запропонувати зрозуміти?

(Бічна примітка: я дійсно не знаю, як позначити це. Пропозиції вітаються.)

Редагувати: Дозвольте уточнити, що якщо я можу отримати список можливих значень для X, я можу зробити інші тести, які допоможуть мені звузити його до початкового значення.



1
@rwong: ваш коментар - єдина правильна відповідь.
кевін клайн

Так, і метод Ейлера здається особливо ефективним, оскільки факторизація mстановить всього 2 ^ 32 або 2 ^ 64, плюс експоненція aмодуля mпроста (просто ігноруйте переповнення там)
MSalters

1
Я думаю, що особлива проблема насправді є раціональною реконструкцією
MSalters

1
@MSalters: Ні, саме там у вас є r*s^-1 mod mі вам потрібно знайти і те, rі s. Ось у нас є, r*s mod mі ми знаємо все, але r.
user2357112 підтримує Моніку

Відповіді:


50

Помножте на 1041204193.

Коли результат множення не вписується в int, точного результату ви не отримаєте, але отримаєте число, еквівалентне точному результату за модулем 2 ** 32 . Це означає, що якщо число, яке ви помножили, було одночасно на 2 ** 32 (що означає, що воно повинно бути непарним), ви можете помножити на його мультиплікативний зворотний, щоб повернути ваше число. Вольфрам Альфа або розширений евклідовий алгоритм може сказати нам, що мультиплікативний зворотний модуль 2 ** 32 є 1041204193. Отже, помножте на 1041204193, і у вас є вихідний х назад.

Якби у нас, скажімо, 60 замість 33, ми не змогли би відновити початкове число, але ми змогли б звузити його до кількох можливостей. Обчисливши коефіцієнт 60 на 4 * 15, обчисливши обернену 15 мод 2 ** 32, і помноживши на це, ми можемо відновити в 4 рази більше вихідного числа, залишивши лише 2 біти високого порядку числа на грубу силу. Wolfram Alpha дає нам 4008636143 для зворотного, який не вміщується в int, але це нормально. Ми просто знаходимо число, еквівалентне 4008636143 mod 2 ** 32, або примушуємо його до int все одно, щоб компілятор зробив це для нас, і результат також буде оберненим 15 mod 2 ** 32. ( Ми отримуємо -286331153. )


5
О, малюк. Тому вся робота, яку мій комп'ютер зробив над створенням карти, вже була зроблена Евклідом.
v010dya

21
Мені подобається суть фактичності у вашому першому реченні. "О, це 1041204193, звичайно. Хіба ви цього не запам'ятали?" :-P
Doorknob

2
Було б корисно показати приклад цього роботи для декількох чисел, наприклад, такого, де х * 33 не переповнюється, і такого, де він є.
Роб Уоттс

2
Розум подув. Ого.
Майкл Газонда

4
Вам не знадобиться ні Евклід, ні Вольфрам Альфа (звичайно!), Щоб знайти зворотну частину 33 модуля $ 2 ^ {32} $. Оскільки $ x = 32 = 2 ^ 5 $ є нільпотентним (порядку $ 7 $) модулем $ 2 ^ 32 $, ви можете просто застосувати тотожність геометричного ряду $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (після чого ряд обривається), щоб знайти число $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $, що становить 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Це, можливо, краще підходить як питання до Math (sic) SE. Ви в основному маєте справу з модульною арифметикою, оскільки викидання лівих бітів - це одне і те ж.

Я не такий хороший у математиці, як люди, які перебувають на Math (sic) SE, але я спробую відповісти.

У нас є те, що число перемножується на 33 (3 * 11), і єдиним його спільним знаменником у вашому моді є 1. Це тому, що за визначенням біти в комп'ютері мають потужність дві, і, отже, ваш мод деяка сила двох.

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

Якби це не 33, а прем'єр чи якась сила прем'єр-міністра, я вважаю, що відповідь була б так, але в цьому випадку ... запитайте на Math.SE!

Тест на програму

Це в C ++, тому що я не знаю C #, але ця концепція все-таки дотримується. Це, схоже, показує, що ви можете:

#include <iostream>
#include <map>

int main(void)
{
    unsigned short count = 0;
    unsigned short x = 0;
    std::map<unsigned short, unsigned short> nextprev;

    nextprev[0] = 0;
    while(++x) nextprev[x] = 0;

    unsigned short nextX;
    while(++x)
    {
            nextX = x*33;
            if(nextprev[nextX])
            {
                    std::cout << nextprev[nextX] << "*33==" << nextX << " && " << x << "*33==" << nextX << std::endl;
                    ++count;
            }
            else
            {
                    nextprev[nextX] = x;
                    //std::cout << x << "*33==" << nextX << std::endl;
            }
    }

    std::cout << count << " collisions found" << std::endl;

    return 0;
}

Після заповнення такої карти ви завжди зможете отримати попередній X, якщо знаєте наступну. За весь час існує лише одне значення.


Чому працювати з невід’ємним типом даних буде простіше? Чи не підписані та непідписані обробляються однаково на комп’ютері, лише їх формат виводу людини відрізняється?
Xcelled

@ Xcelled194 Ну, мені простіше думати про ці цифри.
v010дя

Досить справедливий xD Людський фактор ~
Xcelled

Я видалив це твердження про негативне, щоб зробити його більш очевидним.
v010dya

1
@ Xcelled194: Непідписані типи даних відповідають звичайним правилам модульної арифметики; підписані типи не мають. Зокрема, maxval+1це 0 лише для неподписаних типів.
MSalters

2

Один із способів отримати це - використовувати грубу силу. Вибачте, що я не знаю C #, але наступним є c-подібний псевдо-код для ілюстрації рішення:

for (x=0; x<=INT_MAX; x++) {
    if (x*33 == test_value) {
        printf("%d\n", x);
    }
}

Технічно все, що вам потрібно, x*33%(INT_MAX+1) == test_valueале целочисельне переповнення автоматично зробить %для вас операцію, якщо ваша мова не використовує довільні точні цілі числа (bigint).

Це дає вам серію чисел, яка, можливо, була початковою цифрою. Першим надрукованим номером було б число, яке створило б один раунд переповнення. Другим числом було б число, яке створило б два раунди переповнення. І так далі..

Отже, якщо ви краще знаєте свої дані, ви можете краще здогадатися. Наприклад, звичайна математика годинника (переповнення кожні 12 годин), як правило, робить перше число більш імовірним, оскільки більшість людей цікавляться сьогоднішніми справами.


C # поводиться як C з основними типами - тобто intце 4-байтне підписане ціле число, яке завершується, тому ваша відповідь все-таки хороша, хоча грубе форсування було б не найкращим способом пройти, якщо у вас є багато входів! :)
Xcelled

Так, я намагався робити це на папері з правилами по модулю алгебри тут: math.stackexchange.com/questions/346271 / ... . Але я застряг, намагаючись розібратися в цьому, і закінчився
жорстоким

Цікава стаття, хоча мені доведеться вивчити її трохи глибше, щоб вона клацнула, я думаю.
Xcelled

@slebetman Подивіться на мій код. Здається, є лише одна відповідь, коли мова йде про множення на 33.
v010dya

2
Виправлення: C intгарантовано не завершиться (див. Документи вашого компілятора). Однак це правда для неподписаних типів.
Томас Едінг

1

Ви можете SMT-вирішувач Z3 попросити його дати вам задоволення для формули x * 33 = valueFromFile. Це перетворить це рівняння для вас і дасть вам усі можливі значення x. Z3 підтримує точну арифметику біттера, включаючи множення.

    public static void InvertMultiplication()
    {
        int multiplicationResult = new Random().Next();
        int knownFactor = 33;

        using (var context = new Context(new Dictionary<string, string>() { { "MODEL", "true" } }))
        {
            uint bitvectorSize = 32;
            var xExpr = context.MkBVConst("x", bitvectorSize);
            var yExpr = context.MkBVConst("y", bitvectorSize);
            var mulExpr = context.MkBVMul(xExpr, yExpr);
            var eqResultExpr = context.MkEq(mulExpr, context.MkBV(multiplicationResult, bitvectorSize));
            var eqXExpr = context.MkEq(xExpr, context.MkBV(knownFactor, bitvectorSize));

            var solver = context.MkSimpleSolver();
            solver.Assert(eqResultExpr);
            solver.Assert(eqXExpr);

            var status = solver.Check();
            Console.WriteLine(status);
            if (status == Status.SATISFIABLE)
            {
                Console.WriteLine(solver.Model);
                Console.WriteLine("{0} * {1} = {2}", solver.Model.Eval(xExpr), solver.Model.Eval(yExpr), solver.Model.Eval(mulExpr));
            }
        }
    }

Вихід виглядає приблизно так:

SATISFIABLE
(define-fun y () (_ BitVec 32)
  #xa33fec22)
(define-fun x () (_ BitVec 32)
  #x00000021)
33 * 2738875426 = 188575842

0

Якщо скасувати цей результат, ви отримаєте ненульову кінцеву кількість чисел (як правило, нескінченна, але intє кінцевим підмножиною ℤ). Якщо це прийнятно, просто генеруйте числа (див. Інші відповіді).

В іншому випадку вам потрібно підтримувати список історії (обмеженої чи нескінченної довжини) історії змінної.


0

Як завжди, є рішення вченого і рішення від інженера.

Вище ви знайдете дуже вдале рішення від вченого, яке працює завжди, але вимагає від вас обчислити "мультиплікативний зворотний".

Ось швидке рішення від інженера, яке не змусить вас спробувати всі можливі цілі числа.

val multiplier = 33 //used with 0x23456789
val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL

val overflowBit = 0x100000000L
for(test <- 0 until multiplier) {
  if((problemAsLong + overflowBit * test) % multiplier == 0) {
    val originalLong = (problemAsLong + overflowBit * test) / multiplier
    val original = originalLong.toInt
    println(s"$original (test = $test)")
  }
}

Які ідеї?

  1. У нас переповнення, тому давайте використовувати великі типи для відновлення ( Int -> Long)
  2. Ми, ймовірно, втратили деякі біти через переповнення, відновимо їх
  3. Перелив був не більше Int.MaxValue * multiplier

Повний виконуваний код розміщено на веб- сайті http://ideone.com/zVMbGV

Деталі:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Тут ми перетворюємо наш збережений номер у Long, але оскільки підписані Int і Long, ми повинні зробити це правильно.
    Таким чином, ми обмежуємо число, використовуючи побітові І, бітами Int.
  • val overflowBit = 0x100000000L
    Цей біт або його множення може бути втрачений початковим множенням.
    Це перший шматочок поза діапазоном Int.
  • for(test <- 0 until multiplier)
    Згідно 3-ї ідеї максимальний перелив обмежений множником, тому не намагайтеся більше, ніж нам дійсно потрібно.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Перевірте, додавши, можливо, втрачений перелив, ми підійшли до рішення
  • val original = originalLong.toInt
    Оригінальна проблема була в діапазоні Int, тому повернемося до неї. В іншому випадку ми могли неправильно відновити числа, які були негативними.
  • println(s"$original (test = $test)")
    Не переривайтесь після першого рішення, бо можуть бути й інші можливі рішення.

PS: 3-я ідея не є строго правильною, але залишається зрозумілою.
Int.MaxValueє 0x7FFFFFFF, але максимальний перелив є 0xFFFFFFFF * multiplier.
Тож правильний текст буде "Переповнення було не більше -1 * multiplier".
Це правильно, але не всі зрозуміють це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.