Bir tamsayı sarmalamasının "geri alınması"


20

Birkaç yıl önce ilginç bir teorik sorunla karşılaştım. Hiç bir çözüm bulamadım ve uyuduğumda beni rahatsız ediyor.

Diyelim ki x adında bir int içinde bir sayı tutan bir (C #) uygulamanız var. (X değeri sabit değildir). Program çalıştırıldığında x, 33 ile çarpılır ve daha sonra bir dosyaya yazılır.

Temel kaynak kodu şuna benzer:

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

Birkaç yıl sonra, X'in orijinal değerlerine ihtiyacınız olduğunu keşfedersiniz. Bazı hesaplamalar basittir: Dosyadaki sayıyı 33'e bölün. Ancak, diğer durumlarda X, çarpmanın bir tamsayı taşmasına neden olacak kadar büyüktür. Dokümanlara göre , C #, sayı daha küçük olana kadar yüksek dereceli bitleri kesecektir int.MaxValue. Bu durumda, şunlardan biri mümkün müdür:

  1. X'in kendisini kurtarın veya
  2. X için olası değerlerin bir listesi kurtarılsın mı?

Bana öyle geliyor (mantığım kesinlikle kusurlu olsa da), bir veya her ikisinin de mümkün olması gerekiyor, çünkü daha basit ekleme durumu işe yarıyor (Esasen 10'a X eklerseniz ve sarılırsa, 10'u çıkarabilir ve X ile tekrar sarılabilirsiniz. ) ve çarpma basitçe tekrarlanan ekleme. Ayrıca yardım etmek (inanıyorum), X'in her durumda aynı değerle çarpılmasıdır - sabit 33.

Bu yıllardır garip anlarda kafatasımın etrafında dans ediyor. Bu benim başıma gelecek, düşünmek için biraz zaman harcayacağım ve sonra birkaç ay boyunca unutacağım. Bu sorunu kovalamaktan bıktım! Biri fikir verebilir mi?

(Yan not: Bunu nasıl etiketleyeceğimi gerçekten bilmiyorum. Öneriler hoş geldiniz.)

Düzenleme: X için olası değerlerin bir listesini alabilir, orijinal değerine daraltmak için yapabileceğim başka testler olduğunu açıklığa kavuşturalım.



1
@rwong: Yorumunuz tek doğru cevaptır.
kevin cline

Çarpanlara beri Evet, ve Euler yöntemi özellikle etkili görünüyor msadece 2 ^ 32 veya ^ 64 2, artı üssünün amodulo m(sadece taşma orada görmezden) basittir
MSalters

1
Bence bu problem aslında Rasyonel Yeniden Yapılanma
MSalters

1
@MSalters: Hayır, en nerede olduğunu r*s^-1 mod mve her iki bulmalıyız rve s. Burada, r*s mod mherşeyi biliyoruz ve biliyoruz ama r.
user2357112

Yanıtlar:


50

1041204193 ile çarpın.

Bir çarpmanın sonucu bir int'e uymadığında, kesin sonucu elde edemezsiniz, ancak kesin sonuç modulo 2 ** 32'ye eşdeğer bir sayı alırsınız . Bu, çarptığınız sayının 2 ** 32'ye çarpması (yani sadece garip olması gerektiği anlamına geliyorsa), numaranızı geri almak için çarpım tersiyle çarpabileceğiniz anlamına gelir . Wolfram Alpha veya genişletilmiş Öklid algoritması bize 33'ün çarpımsal ters modulo 2 ** 32'sinin 1041204193 olduğunu söyleyebilir. Yani, 1041204193 ile çarpın ve orijinal x geri.

Diyelim ki 33 yerine 60 olsaydı, orijinal numarayı kurtaramazdık, ancak birkaç olasılıkla daraltabilirdik. 60'ı 4 * 15'e çarpanlara ayırarak, 15 mod 2 ** 32'nin tersini hesaplayarak ve bununla çarparak, sayının sadece 2 yüksek dereceli bitini kaba kuvvete bırakarak orijinal sayının 4 katını kurtarabiliriz. Wolfram Alpha bize int için uygun olmayan 4008636143 verir, ama sorun değil. Sadece 4008636143 mod 2 ** 32'ye eşit bir sayı buluyoruz ya da derleyicinin bunu bizim için yapmasını sağlamak için bir int'e zorlarız ve sonuç 15 mod 2 ** 32'nin tersi de olacaktır. ( -286331153 olsun. )


5
Ah oğlum. Böylece, bilgisayarımın haritayı oluşturmak için yaptığı tüm çalışmalar zaten Euclid tarafından yapıldı.
v010dya

21
İlk cümlenizdeki olguyu seviyorum. "Ah, bu 1041204193, elbette. Bunu ezberlemiyor musun?" :-P
Doorknob

2
Bu çalışmanın bir örneğini x * 33'ün taşmadığı ve diğerinin yaptığı gibi birkaç sayı için göstermek yararlı olacaktır.
Rob Watts

2
Zihin karmaşası. Vay.
Michael Gazonda

4
33 modulo $ 2 {{32} $ 'ın tersini bulmak için ne Euclid ne de WolframAlpha'ya (kesinlikle!) İhtiyacınız yoktur. $ X = 32 = 2 ^ 5 $ nilpotent ($ 7 $ siparişinden) modulo $ 2 ^ 32 $ olduğundan, sadece $ (1 + x) ^ {- 1} = 1-x + x ^ geometrik seri kimliğini uygulayabilirsiniz. 2-x ^ 3 + \ cdots + x ^ 6 $ (bundan sonra serinin kopması) $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Bu Math (sic) SE için bir soru olarak daha uygun olabilir. Temel olarak modüler aritmetik ile uğraşıyorsunuz, çünkü en soldaki bitleri bırakmak aynı şeydir.

Maths'ta Math (sic) SE'de olan insanlar kadar iyi değilim, ama cevap vermeye çalışacağım.

Burada sahip olduğumuz şey, sayının 33 (3 * 11) ile çarpılması ve modunuzla tek ortak paydası 1'dir. Çünkü tanım gereği bilgisayardaki bitler iki güçtür ve dolayısıyla modunuz ikisinin gücü.

Her önceki değer için aşağıdaki değeri hesapladığınız tabloyu oluşturabilirsiniz. Ve soru şu olur, aşağıdaki sayılar bir öncekine karşılık gelir.

Eğer 33 olmasaydı, bir asal ya da bir asalın gücü olsaydı, cevabın evet olacağına inanıyorum, ama bu durumda… Math.SE'ye sor!

Programlı test

C # bilmiyorum çünkü bu C ++, ama kavram hala tutar. Bu, şunları yapabileceğinizi gösteriyor:

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

Böyle bir haritayı doldurduktan sonra, bir sonrakini biliyorsanız her zaman bir önceki X'i elde edebilirsiniz. Her zaman sadece tek bir değer vardır.


Negatif olmayan bir veri türüyle çalışmak neden daha kolay olur? İmzalanmış ve imzalanmamış bilgisayarda aynı şekilde işlem yapılmıyor, yalnızca insan çıktı biçimleri farklı mı?
Xcelled

@ Xcelled194 Bu sayıları düşünmem daha kolay.
v010dya

Yeterince adil xD İnsan faktörü ~
Xcelled

Daha açık hale getirmek için olumsuz olmayan hakkındaki ifadeyi kaldırdım.
v010dya

1
@ Xcelled194: İmzasız veri türleri, modüler aritmetiğin olağan kurallarına uyar; imzalı türler yoktur. Özellikle, maxval+1yalnızca işaretsiz türler için 0'dır.
MSalters

2

Bunu almanın bir yolu kaba kuvvet kullanmaktır. Üzgünüm C # bilmiyorum ama aşağıdaki çözümü göstermek için c-benzeri sahte kod:

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

Teknik olarak, ihtiyacınız olan şeydir, x*33%(INT_MAX+1) == test_valueancak %diliniz keyfi hassas tamsayılar (bigint) kullanmadığı sürece tamsayı taşması işlemi sizin için otomatik olarak yapar .

Bu size orijinal sayı olabilecek bir sayı dizisidir. Yazdırılan ilk sayı, bir taşma turu oluşturacak sayıdır. İkinci sayı, iki tur taşma oluşturacak sayıdır. Ve bunun gibi..

Dolayısıyla, verilerinizi daha iyi biliyorsanız daha iyi bir tahmin yapabilirsiniz. Örneğin, yaygın saat matematiği (saat 12'de taşma) ilk sayıyı daha muhtemel hale getirir, çünkü çoğu insan bugün olan şeylerle ilgilenir.


C # temel tiplerle C gibi davranır - yani intsaran 4 bayt imzalı bir tamsayıdır, bu yüzden cevabınız hala iyidir, ancak çok fazla girdiniz varsa kaba zorlama gitmek için en iyi yol olmaz! :)
Xcelled

Evet, buradan modulo cebir kuralları ile kağıt üzerinde yapmaya çalıştım: math.stackexchange.com/questions/346271/… . Ama anlamaya çalışırken takılıp kaldım ve kaba kuvvetli bir çözüm
buldum

İlginç bir makale, tıklamak için biraz daha derinlemesine çalışmam gerekecek diye düşünüyorum.
Xcelled

@slebetman Koduma bak. Görünüşe göre 33 ile çarpma söz konusu olduğunda tek bir cevap var.
v010dya

2
Düzeltme: C'nin intetrafı sarması garanti edilmez (derleyicinizin belgelerine bakın). İmzasız tipler için de geçerlidir.
Thomas Eding

1

SMT çözücü Z3'ten, size formül için tatmin edici bir görev vermesini isteyebilirsiniz x * 33 = valueFromFile. Bu denklemi sizin için tersine çevirir ve size tüm olası değerleri verir x. Z3, çarpma dahil olmak üzere tam bitvector aritmetiğini destekler.

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

Çıktı şöyle görünür:

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

0

Bu sonucu geri almak size sıfır olmayan bir sonlu sayı verecektir (normalde sonsuzdur, ancak intℤ'nin sonlu bir alt kümesidir). Bu kabul edilebilir durumdaysa, yalnızca sayıları oluşturun (diğer yanıtlara bakın).

Aksi takdirde, değişkenin geçmişinin (sonlu veya sonsuz uzunlukta) bir listesini tutmanız gerekir.


0

Her zaman olduğu gibi, bir bilim adamının bir çözümü ve bir mühendisin çözümü vardır.

Yukarıda, her zaman çalışan, ancak “çarpımsal tersi” hesaplamanızı gerektiren bir bilim insanından çok iyi bir çözüm bulacaksınız.

İşte sizi olası tüm tam sayıları denemeye zorlamayacak hızlı bir çözüm mühendisi.

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

Fikirler neler?

  1. Taşma var, bu yüzden kurtarmak için daha büyük türleri kullanalım ( Int -> Long)
  2. Taşma nedeniyle muhtemelen bazı bitleri kaybettik, onları kurtaralım
  3. Taşma şu değerden fazla değildi: Int.MaxValue * multiplier

Tam yürütülebilir kod http://ideone.com/zVMbGV adresinde bulunur.

Detaylar:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Burada depolanan sayımızı Long'a dönüştürüyoruz, ancak Int ve Long imzalandığından, doğru bir şekilde yapmalıyız.
    Bu yüzden sayıyı bit ve AND bitlerini Int.
  • val overflowBit = 0x100000000L
    Bu bit veya çarpımı ilk çarpma ile kaybedilebilir.
    Int aralığının dışında ilk bit.
  • for(test <- 0 until multiplier)
    3. Fikire göre, maksimum taşma çarpanla sınırlıdır, bu yüzden gerçekten ihtiyacımızdan daha fazlasını denemeyin.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Muhtemelen kayıp taşma ekleyerek bir çözüme ulaşıp ulaşmadığımızı kontrol edin
  • val original = originalLong.toInt
    Orijinal sorun Int aralığındaydı, bu yüzden geri dönelim. Aksi takdirde, negatif olan sayıları yanlış bir şekilde kurtarabiliriz.
  • println(s"$original (test = $test)")
    İlk çözümden sonra kırılmayın, çünkü başka olası çözümler de olabilir.

Not: 3. Fikir kesinlikle doğru değil, ancak anlaşılabilir olması için bırakıldı.
Int.MaxValueolduğunu 0x7FFFFFFF, ancak maksimal taşma olduğunu 0xFFFFFFFF * multiplier.
Yani doğru metin “Taşma fazla değildi -1 * multiplier” olacaktır.
Bu doğru, ama herkes bunu anlamayacak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.