Dịch vụ hoàn thành


20

Tôi gặp phải một vấn đề lý thuyết thú vị một số năm trước. Tôi không bao giờ tìm thấy một giải pháp, và nó tiếp tục ám ảnh tôi khi tôi ngủ.

Giả sử bạn có một ứng dụng (C #) chứa một số số trong một int, được gọi là x. (Giá trị của x không cố định). Khi chương trình được chạy, x được nhân với 33 và sau đó được ghi vào một tệp.

Mã nguồn cơ bản trông như thế này:

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

Vài năm sau, bạn phát hiện ra rằng bạn cần các giá trị ban đầu của X trở lại. Một số tính toán rất đơn giản: Chỉ cần chia số trong tệp cho 33. Tuy nhiên, trong các trường hợp khác, X đủ lớn để phép nhân gây ra tràn số nguyên. Theo các tài liệu , C # sẽ cắt các bit thứ tự cao cho đến khi số lượng nhỏ hơn int.MaxValue. Có thể, trong trường hợp này, một trong hai:

  1. Tự phục hồi X hoặc
  2. Khôi phục danh sách các giá trị có thể có cho X?

Dường như với tôi (mặc dù logic của tôi chắc chắn có thể bị sai sót) rằng một hoặc cả hai nên có thể, vì trường hợp bổ sung đơn giản hơn (Về cơ bản nếu bạn thêm 10 vào X và nó kết thúc, bạn có thể trừ 10 và kết thúc với X một lần nữa ) và phép nhân chỉ đơn giản là lặp lại bổ sung. Cũng giúp (tôi tin) là thực tế rằng X được nhân với cùng một giá trị trong mọi trường hợp - một hằng số 33.

Điều này đã nhảy múa xung quanh hộp sọ của tôi tại những thời điểm kỳ lạ trong nhiều năm. Nó sẽ xảy ra với tôi, tôi sẽ dành thời gian cố gắng suy nghĩ về nó, và rồi tôi sẽ quên nó trong vài tháng. Tôi mệt mỏi vì phải theo đuổi vấn đề này! Bất cứ ai có thể cung cấp cái nhìn sâu sắc?

(Lưu ý bên lề: Tôi thực sự không biết cách gắn thẻ này. Đề xuất chào mừng.)

Chỉnh sửa: Hãy để tôi làm rõ rằng nếu tôi có thể nhận được danh sách các giá trị có thể có cho X, có những thử nghiệm khác tôi có thể làm để giúp tôi thu hẹp nó xuống giá trị ban đầu.


13
Một cái gì đó dọc theo dòng en.wikipedia.org/wiki/Modular_multiplicative_inverse
rwong

1
@rwong: bình luận của bạn là câu trả lời đúng duy nhất.
kevin cline

Phương pháp của Yup và Euler có vẻ đặc biệt hiệu quả vì hệ số của mchỉ là 2 ^ 32 hoặc 2 ^ 64, cộng với phép lũy thừa của amodulo mrất đơn giản (chỉ cần bỏ qua tràn vào đó)
MSalters

1
Tôi nghĩ rằng vấn đề cụ thể trong thực tế là Tái thiết Rational
MSalters

1
@MSalters: Không, đó là nơi bạn có r*s^-1 mod mvà bạn cần tìm cả hai rs. Ở đây, chúng tôi có r*s mod mvà chúng tôi biết tất cả mọi thứ nhưng r.
user2357112 hỗ trợ Monica

Câu trả lời:


50

Nhân với 1041204193.

Khi kết quả của phép nhân không khớp với số nguyên, bạn sẽ không nhận được kết quả chính xác, nhưng bạn sẽ nhận được một số tương đương với kết quả chính xác modulo 2 ** 32 . Điều đó có nghĩa rằng nếu số bạn nhân là nguyên tố cùng nhau đến 2 ** 32 (mà chỉ có nghĩa là nó phải được lẻ), bạn có thể nhân với nó nghịch đảo để có được số trở lại của bạn. Wolfram Alpha hoặc thuật toán Euclide mở rộng có thể cho chúng ta biết modulo nghịch đảo số nhân 2 ** 32 là 1041204193. Vì vậy, nhân với 1041204193 và bạn có x gốc.

Nếu chúng ta có 60, thay vì 33, chúng ta sẽ không thể phục hồi số ban đầu, nhưng chúng ta có thể thu hẹp nó xuống một vài khả năng. Bằng cách bao gồm 60 thành 4 * 15, tính toán nghịch đảo của 15 mod 2 ** 32 và nhân với đó, chúng ta có thể phục hồi gấp 4 lần số ban đầu, chỉ để lại 2 bit thứ tự cao của số cho lực lượng vũ phu. Wolfram Alpha cung cấp cho chúng tôi 4008636143 cho nghịch đảo, không phù hợp với int, nhưng không sao. Chúng tôi chỉ tìm thấy một số tương đương với 4008636143 mod 2 ** 32 hoặc buộc nó vào một int dù sao để trình biên dịch làm điều đó cho chúng tôi, và kết quả cũng sẽ là nghịch đảo của 15 mod 2 ** 32. ( Chúng tôi nhận được -286331153. )


5
Oh Boy. Vì vậy, tất cả các công việc mà máy tính của tôi đã thực hiện để xây dựng bản đồ đã được thực hiện bởi Euclid.
v010dya

21
Tôi thích vấn đề thực tế trong câu đầu tiên của bạn. "Ồ, đó là 1041204193, tất nhiên. Bạn không nhớ điều đó à?" :-P
Doorknob

2
Sẽ rất hữu ích khi đưa ra một ví dụ về điều này hoạt động cho một vài số, chẳng hạn như một trong đó x * 33 không tràn và một nơi mà nó đã làm.
Rob Watts

2
Tâm thổi. Ồ
Michael Gazonda

4
Bạn không cần Euclid hay WolframAlpha (chắc chắn!) Để tìm nghịch đảo của 33 modulo $ 2 ^ {32} $. Vì $ x = 32 = 2 ^ 5 $ là nilpotent (của đơn hàng $ 7 $) modulo $ 2 ^ 32 $, bạn chỉ có thể áp dụng nhận dạng chuỗi hình học $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (sau đó chuỗi bị ngắt) để tìm số $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ là $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Điều này có thể phù hợp hơn như là một câu hỏi cho Math (sic) SE. Về cơ bản, bạn đang xử lý số học mô-đun, vì việc bỏ đi các bit bên trái là điều tương tự.

Tôi không giỏi môn Toán như những người học môn Toán (sic) SE, nhưng tôi sẽ cố gắng trả lời.

Những gì chúng ta có ở đây là số đang được nhân với 33 (3 * 11) và mẫu số chung duy nhất của nó với mod của bạn là 1. Đó là bởi vì theo định nghĩa, các bit trong máy tính là lũy thừa của hai, và do đó mod của bạn là sức mạnh của hai.

Bạn sẽ có thể xây dựng bảng trong đó với mỗi giá trị trước đó bạn tính giá trị sau. Và câu hỏi trở thành các số sau chỉ tương ứng với một số trước đó.

Nếu nó không phải là 33, mà là một số nguyên tố hoặc một số sức mạnh của một số nguyên tố, tôi tin rằng câu trả lời sẽ là có, nhưng trong trường hợp này, hãy hỏi Math.SE!

Kiểm tra chương trình

Đây là trong C ++ vì tôi không biết C #, nhưng khái niệm này vẫn còn. Điều này dường như cho thấy rằng bạn có thể:

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

Sau khi điền một bản đồ như vậy, bạn sẽ luôn có thể nhận được X trước đó nếu bạn biết bản đồ tiếp theo. Chỉ có một giá trị duy nhất tại mọi thời điểm.


Tại sao làm việc với một kiểu dữ liệu không âm sẽ dễ dàng hơn? Không được ký và không dấu được xử lý theo cùng một cách trong máy tính, chỉ có định dạng đầu ra con người của họ khác nhau?
Xcelled

@ Xcelled194 Chà, tôi dễ nghĩ hơn về những con số này.
v010dya

Đủ công bằng xD Yếu tố con người ~
Xcelled

Tôi đã loại bỏ tuyên bố đó về không tiêu cực để làm cho nó rõ ràng hơn.
v010dya

1
@ Xcelled194: Các kiểu dữ liệu không được gán theo các quy tắc thông thường của số học mô-đun; ký loại không. Cụ thể, maxval+1là 0 chỉ cho các loại không dấu.
MSalters

2

Một cách để có được nó là sử dụng vũ lực. Xin lỗi tôi không biết C # nhưng sau đây là mã giả giống như c để minh họa cho giải pháp:

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

Về mặt kỹ thuật, những gì bạn cần là x*33%(INT_MAX+1) == test_valuenhưng tràn số nguyên sẽ tự động thực hiện %thao tác cho bạn trừ khi ngôn ngữ của bạn sử dụng các số nguyên chính xác tùy ý (bigint).

Điều này mang lại cho bạn là một chuỗi các số có thể là số ban đầu. Số đầu tiên được in sẽ là số tạo ra một vòng tràn. Số thứ hai sẽ là số tạo ra hai vòng tràn. Và cứ thế ..

Vì vậy, nếu bạn biết dữ liệu của mình tốt hơn, bạn có thể đoán chính xác hơn. Ví dụ, toán học đồng hồ thông thường (tràn vào cứ sau 12 giờ) có xu hướng làm cho số đầu tiên có nhiều khả năng hơn vì hầu hết mọi người đều quan tâm đến những điều xảy ra ngày hôm nay.


C # hoạt động giống như C với các loại cơ bản - tức intlà số nguyên có ký hiệu 4 byte bao bọc, vì vậy câu trả lời của bạn vẫn tốt, mặc dù cưỡng bức sẽ không phải là cách tốt nhất nếu bạn có nhiều đầu vào! :)
Xcelled

Vâng, tôi đã thử làm nó trên giấy với các quy tắc đại số modulo từ đây: math.stackexchange.com/questions/346271/ . Nhưng tôi đã bị mắc kẹt khi cố gắng tìm ra nó và kết thúc bằng một giải pháp vũ phu :)
slebetman

Bài viết thú vị, mặc dù tôi sẽ phải nghiên cứu sâu hơn một chút để nó nhấp, tôi nghĩ vậy.
Xcelled

@slebetman Nhìn vào mã của tôi. Dường như chỉ có một câu trả lời duy nhất khi nhân với 33.
v010dya

2
Sửa chữa: C intkhông được bảo đảm để bọc xung quanh (xem tài liệu của nhà soạn nhạc của bạn). Điều đó đúng với các loại không dấu mặc dù.
Thomas Eding

1

Bạn có thể bộ giải Z3 của SMT để yêu cầu nó cung cấp cho bạn một bài tập thỏa mãn cho công thức x * 33 = valueFromFile. Nó sẽ đảo ngược phương trình đó cho bạn và cung cấp cho bạn tất cả các giá trị có thể có của x. Z3 hỗ trợ số học bitvector chính xác bao gồm cả phép nhân.

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

Đầu ra trông như thế này:

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

0

Để hoàn tác kết quả đó sẽ cung cấp cho bạn một số lượng hữu hạn khác không (thường là vô hạn, nhưng intlà tập con hữu hạn của). Nếu điều này được chấp nhận, chỉ cần tạo ra các số (xem câu trả lời khác).

Mặt khác, bạn cần duy trì một danh sách lịch sử (có độ dài hữu hạn hoặc vô hạn) của lịch sử của biến.


0

Như mọi khi, có một giải pháp từ một nhà khoa học và giải pháp từ một kỹ sư.

Ở trên, bạn sẽ tìm thấy một giải pháp rất tốt từ một nhà khoa học, luôn luôn hoạt động, nhưng đòi hỏi bạn phải tính toán nghịch đảo số nhân.

Đây là một giải pháp nhanh chóng từ kỹ sư, sẽ không buộc bạn phải thử tất cả các số nguyên có thể.

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

Ý tưởng là gì?

  1. Chúng tôi đã bị tràn, vì vậy hãy sử dụng các loại lớn hơn để khôi phục ( Int -> Long)
  2. Có lẽ chúng tôi đã mất một số bit do tràn, hãy khôi phục chúng
  3. Tràn không quá Int.MaxValue * multiplier

Mã thực thi đầy đủ được đặt trên http://ideone.com/zVMbGV

Chi tiết:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Ở đây chúng tôi chuyển đổi số được lưu trữ của mình thành Long, nhưng vì Int và Long đã được ký, chúng tôi phải thực hiện chính xác.
    Vì vậy, chúng tôi giới hạn số lượng bằng cách sử dụng bitwise AND với các bit của Int.
  • val overflowBit = 0x100000000L
    Số bit hoặc phép nhân này của nó có thể bị mất bởi phép nhân ban đầu.
    Đó là một chút đầu tiên bên ngoài phạm vi Int.
  • for(test <- 0 until multiplier)
    Theo ý tưởng thứ 3, tràn tối đa bị giới hạn bởi cấp số nhân, vì vậy đừng cố gắng nhiều hơn chúng ta thực sự cần.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Kiểm tra xem bằng cách thêm tràn có thể bị mất, chúng tôi đi đến một giải pháp
  • val original = originalLong.toInt
    Vấn đề ban đầu là trong phạm vi Int, vì vậy hãy trở lại với nó. Nếu không, chúng tôi có thể phục hồi số không chính xác, đó là số âm.
  • println(s"$original (test = $test)")
    Đừng nghỉ ngơi sau giải pháp đầu tiên, vì có thể có những giải pháp khả thi khác.

PS: Ý tưởng thứ 3 không hoàn toàn chính xác, nhưng còn lại để có thể hiểu được.
Int.MaxValue0x7FFFFFFF, nhưng tràn tối đa là 0xFFFFFFFF * multiplier.
Vì vậy, văn bản chính xác sẽ là trên đường tràn Không nhiều hơn so với -1 * multiplier.
Điều này là chính xác, nhưng không phải ai cũng sẽ hiểu nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.