"Undoing" sebuah bilangan bulat integer


20

Saya mengalami masalah teoretis yang menarik beberapa tahun yang lalu. Saya tidak pernah menemukan solusi, dan itu terus menghantui saya ketika saya tidur.

Misalkan Anda memiliki aplikasi (C #) yang menyimpan sejumlah angka di int, disebut x. (Nilai x tidak tetap). Ketika program dijalankan, x dikalikan dengan 33 dan kemudian ditulis ke file.

Kode sumber dasar terlihat seperti ini:

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

Beberapa tahun kemudian, Anda menemukan bahwa Anda membutuhkan nilai asli X kembali. Beberapa perhitungan sederhana: Cukup bagi angka dalam file dengan 33. Namun, dalam kasus lain, X cukup besar sehingga perkalian menyebabkan integer overflow. Menurut dokumen , C # akan memotong bit orde tinggi hingga jumlahnya kurang dari int.MaxValue. Apakah mungkin, dalam hal ini, untuk:

  1. Pulihkan X itu sendiri atau
  2. Pulihkan daftar nilai yang mungkin untuk X?

Menurut saya (walaupun logika saya tentu saja cacat) bahwa satu atau keduanya harus dimungkinkan, karena kasus penambahan yang lebih sederhana bekerja (Pada dasarnya jika Anda menambahkan 10 ke X dan membungkus, Anda dapat mengurangi 10 dan berakhir dengan X lagi ) dan perkalian adalah penambahan yang hanya berulang. Juga membantu (saya percaya) adalah fakta bahwa X dikalikan dengan nilai yang sama dalam semua kasus - konstan 33.

Ini telah menari di sekitar tengkorak saya pada saat-saat aneh selama bertahun-tahun. Itu akan terpikir oleh saya, saya akan meluangkan waktu mencoba memikirkannya, dan kemudian saya akan melupakannya selama beberapa bulan. Saya lelah mengejar masalah ini! Adakah yang bisa menawarkan wawasan?

(Catatan: Saya benar-benar tidak tahu cara memberi tag pada yang ini. Saran diterima.)

Sunting: Izinkan saya mengklarifikasi bahwa jika saya bisa mendapatkan daftar nilai yang mungkin untuk X, ada tes lain yang bisa saya lakukan untuk membantu saya mempersempitnya ke nilai asli.



1
@rwong: komentar Anda adalah satu-satunya jawaban yang benar.
kevin cline

Yup, dan metode Euler tampaknya sangat efektif karena faktorisasi mhanya 2 ^ 32 atau 2 ^ 64, ditambah eksponensial amodulo msangat mudah (abaikan saja luapan di sana)
MSalters

1
Saya pikir masalah khusus sebenarnya Rekonstruksi Rasional
MSalters

1
@MSalters: Tidak, itu di mana Anda memiliki r*s^-1 mod mdan Anda perlu untuk menemukan kedua rdan s. Di sini, kita miliki r*s mod mdan kita tahu segalanya kecuali r.
user2357112 mendukung Monica

Jawaban:


50

Kalikan dengan 1041204193.

Ketika hasil perkalian tidak cocok dengan int, Anda tidak akan mendapatkan hasil yang tepat, tetapi Anda akan mendapatkan angka yang setara dengan hasil yang tepat modulo 2 ** 32 . Itu berarti bahwa jika angka yang Anda kalikan dengan coprime menjadi 2 ** 32 (yang berarti angka itu harus ganjil), Anda dapat mengalikan dengan pembalikan multiplikatifnya untuk mendapatkan nomor Anda kembali. Wolfram Alpha atau algoritma Euclidean yang diperluas dapat memberi tahu kami modul multiplikatif inversi 33-an 33-an ** 32 adalah 1041204193. Jadi, kalikan dengan 1041204193, dan Anda memiliki x kembali yang asli.

Jika kita memiliki, katakanlah, 60 bukannya 33, kita tidak akan dapat memulihkan nomor aslinya, tetapi kita akan dapat mempersempitnya ke beberapa kemungkinan. Dengan memfaktorkan 60 menjadi 4 * 15, menghitung kebalikan dari 15 mod 2 ** 32, dan mengalikannya, kita dapat memulihkan 4 kali dari angka aslinya, hanya menyisakan 2 bit orde tinggi dari angka tersebut menjadi brute-force. Wolfram Alpha memberi kami 4008636143 untuk kebalikannya, yang tidak sesuai dengan int, tapi tidak apa-apa. Kami hanya menemukan angka yang setara dengan 4008636143 mod 2 ** 32, atau memaksanya ke int agar kompiler melakukan itu untuk kami, dan hasilnya juga akan menjadi kebalikan dari 15 mod 2 ** 32. ( Kami mendapatkan -286331153. )


5
Oh Boy. Jadi semua pekerjaan yang dilakukan komputer saya untuk membuat peta sudah dilakukan oleh Euclid.
v010dya

21
Saya suka masalah fakta dalam kalimat pertama Anda. "Oh, ini 1041204193, tentu saja. Apakah kamu tidak menghafalnya?" :-P
Gagang pintu

2
Akan sangat membantu untuk menunjukkan contoh ini berfungsi untuk beberapa angka, seperti yang mana x * 33 tidak meluap dan yang lainnya.
Rob Watts

2
Pikiran meledak. Wow.
Michael Gazonda

4
Anda tidak perlu Euclid atau WolframAlpha (tentu saja!) Untuk menemukan kebalikan dari 33 modulo $ 2 ^ {32} $. Karena $ x = 32 = 2 ^ 5 $ tidak ada (dari $ 7 $) modulo $ 2 ^ 32 $, Anda dapat menerapkan identitas deret geometri $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (setelah seri terputus) untuk menemukan angka $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ yaitu $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Ini mungkin lebih cocok sebagai pertanyaan untuk Matematika (s) SE. Anda pada dasarnya berurusan dengan modular aritmatika, karena menjatuhkan bit paling kiri adalah hal yang sama.

Saya tidak sebagus Matematika seperti halnya orang-orang yang ada di Matematika (sic) SE, tetapi saya akan mencoba menjawab.

Yang kami miliki di sini adalah bahwa jumlahnya dikalikan dengan 33 (3 * 11), dan satu-satunya penyebut yang umum dengan mod Anda adalah 1. Itu karena menurut definisi bit di komputer adalah kekuatan dua, dan dengan demikian mod Anda adalah beberapa kekuatan dua.

Anda akan dapat membangun tabel di mana untuk setiap nilai sebelumnya Anda menghitung nilai berikut. Dan pertanyaannya adalah apakah angka-angka berikut hanya sesuai dengan yang sebelumnya.

Jika bukan 33, tetapi prima atau kekuatan prima, saya percaya bahwa jawabannya adalah ya, tetapi dalam hal ini ... tanyakan pada Math.SE!

Tes terprogram

Ini di C ++ karena saya tidak tahu C #, tetapi konsepnya masih berlaku. Ini tampaknya menunjukkan bahwa Anda dapat:

#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;
}

Setelah mengisi peta seperti itu, Anda akan selalu bisa mendapatkan X sebelumnya jika Anda tahu yang berikutnya. Hanya ada satu nilai setiap saat.


Mengapa bekerja dengan tipe data non-negatif lebih mudah? Tidak masuk dan tidak ditandatangani ditangani dengan cara yang sama di komputer, hanya format output manusianya yang berbeda?
Xcelled

@ Xcelled194 Yah, lebih mudah bagi saya untuk memikirkan angka-angka ini.
v010dya

Cukup adil xD Faktor manusia ~
Xcelled

Saya telah menghapus pernyataan tentang non-negatif untuk membuatnya lebih jelas.
v010dya

1
@ Xcelled194: Tipe data yang tidak ditandai mengikuti aturan aritmatika modular yang biasa; tipe yang ditandatangani tidak. Khususnya, maxval+10 hanya untuk tipe yang tidak ditandatangani.
MSalters

2

Salah satu cara untuk mendapatkannya adalah dengan menggunakan brute force. Maaf saya tidak tahu C # tetapi yang berikut adalah kode pseudo seperti-c untuk menggambarkan solusi:

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

Secara teknis, yang Anda butuhkan hanyalah x*33%(INT_MAX+1) == test_valueinteger overflow secara otomatis akan melakukan %operasi untuk Anda kecuali bahasa Anda menggunakan integer presisi sewenang-wenang (bigint).

Apa yang Anda dapatkan di sini adalah serangkaian angka yang mungkin merupakan angka asli. Angka pertama yang dicetak adalah angka yang menghasilkan satu putaran luapan. Angka kedua akan menjadi angka yang akan menghasilkan dua putaran overflow. Dan seterusnya..

Jadi, jika Anda tahu data Anda lebih baik, Anda bisa membuat tebakan yang lebih baik. Sebagai contoh, matematika jam umum (meluap setiap jam 12) cenderung membuat angka pertama lebih mungkin karena kebanyakan orang tertarik pada hal-hal yang terjadi hari ini.


C # berperilaku seperti C dengan tipe dasar - yaitu intbilangan bulat bertanda 4 byte yang membungkus, jadi jawaban Anda masih bagus, meskipun memaksa kasar tidak akan menjadi cara terbaik untuk pergi jika Anda memiliki banyak input! :)
Xcelled

Ya, saya mencoba melakukannya di atas kertas dengan aturan aljabar modulo dari sini: math.stackexchange.com/questions/346271/… . Tapi saya terjebak mencoba untuk mencari tahu dan berakhir dengan solusi brute-force :)
slebetman

Artikel yang menarik, meskipun saya harus mempelajarinya sedikit lebih mendalam untuk mengklik, saya pikir.
Xcelled

@slebetman Lihat kode saya. Tampaknya hanya ada satu jawaban ketika mengalikan dengan 33.
v010dya

2
Koreksi: C inttidak dijamin untuk membungkus (lihat dokumen kompiler Anda). Itu berlaku untuk tipe yang tidak ditandatangani.
Thomas Eding

1

Anda dapat memecahkan SMT Z3 untuk memintanya memberi Anda tugas yang memuaskan untuk formula tersebut x * 33 = valueFromFile. Ini akan membalikkan persamaan itu untuk Anda dan memberi Anda semua nilai yang mungkin dari x. Z3 mendukung aritmatika bitvektor yang tepat termasuk perkalian.

    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));
            }
        }
    }

Output terlihat seperti ini:

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

0

Untuk membatalkan hasil itu akan memberi Anda jumlah angka terbatas non-nol (normalnya tak terbatas, tetapi intadalah himpunan bagian terbatas ℤ). Jika ini dapat diterima, hasilkan saja angkanya (lihat jawaban lain).

Kalau tidak, Anda perlu mempertahankan daftar riwayat (panjang terbatas atau tak terbatas) dari sejarah variabel.


0

Seperti biasa, ada solusi dari seorang ilmuwan dan solusi dari seorang insinyur.

Di atas Anda akan menemukan solusi yang sangat baik dari seorang ilmuwan, yang selalu berhasil, tetapi mengharuskan Anda untuk menghitung "invers multiplikatif".

Ini adalah solusi cepat dari insinyur, yang tidak akan memaksa Anda untuk mencoba semua bilangan bulat yang mungkin.

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)")
  }
}

Apa idenya?

  1. Kami mengalami overflow, jadi mari kita gunakan tipe yang lebih besar untuk memulihkan ( Int -> Long)
  2. Kami mungkin kehilangan beberapa bit karena meluap, mari pulihkan mereka
  3. Overflow tidak lebih dari Int.MaxValue * multiplier

Kode yang dapat dieksekusi penuh terletak di http://ideone.com/zVMbGV

Detail:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Di sini kita mengonversi nomor yang disimpan ke Long, tetapi karena Int dan Long ditandatangani, kita harus melakukannya dengan benar.
    Jadi kami membatasi jumlah menggunakan bitwise DAN dengan bit Int.
  • val overflowBit = 0x100000000L
    Penggandaan atau penggandaan ini bisa hilang dengan penggandaan awal.
    Ini sedikit pertama di luar rentang Int.
  • for(test <- 0 until multiplier)
    Menurut Ide ke-3, luapan maksimal dibatasi oleh pengganda, jadi jangan mencoba lebih dari yang sebenarnya kita butuhkan.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Periksa apakah dengan menambahkan kemungkinan overflow yang hilang, kami menemukan solusi
  • val original = originalLong.toInt
    Masalah aslinya ada di kisaran Int, jadi mari kita kembali ke sana. Kalau tidak, kita bisa salah memulihkan nomor, yang negatif.
  • println(s"$original (test = $test)")
    Jangan merusak solusi pertama, karena mungkin ada solusi lain yang mungkin.

PS: Gagasan ke-3 tidak sepenuhnya benar, tetapi dibiarkan begitu agar bisa dimengerti.
Int.MaxValueadalah 0x7FFFFFFF, tetapi overflow maksimal adalah 0xFFFFFFFF * multiplier.
Jadi teks yang benar adalah "Overflow tidak lebih dari -1 * multiplier".
Ini benar, tetapi tidak semua orang akan memahaminya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.