“ เลิกทำ” การสรุปจำนวนเต็ม


20

ฉันพบปัญหาทางทฤษฎีที่น่าสนใจเมื่อหลายปีก่อน ฉันไม่เคยพบวิธีแก้ปัญหาและยังคงหลอกหลอนฉันเมื่อฉันนอนหลับ

สมมติว่าคุณมีแอปพลิเคชัน (C #) ที่มีตัวเลขอยู่ใน int ที่เรียกว่า x (ค่าของ 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 และมัน wraps คุณสามารถลบ 10 ) และการคูณนั้นเติมซ้ำ การช่วยเหลือ (ฉันเชื่อด้วย) ก็คือความจริงที่ว่า X คูณด้วยค่าเดียวกันในทุกกรณี - ค่าคงที่ 33

สิ่งนี้ได้เต้นรอบกะโหลกศีรษะของฉันในช่วงเวลาที่แปลกมาหลายปี มันจะเกิดขึ้นกับฉันฉันจะใช้เวลาพยายามคิดถึงมันแล้วฉันจะลืมมันไปสองสามเดือน ฉันเบื่อที่จะไล่ตามปัญหานี้! ทุกคนสามารถให้ข้อมูลเชิงลึกได้หรือไม่

(หมายเหตุด้านข้าง: ฉันไม่รู้วิธีติดแท็กนี้จริงๆข้อเสนอแนะยินดีต้อนรับ)

แก้ไข:ฉันขอชี้แจงว่าถ้าฉันสามารถรับรายการค่าที่เป็นไปได้สำหรับ X มีการทดสอบอื่น ๆ ที่ฉันสามารถทำได้เพื่อช่วย จำกัด ให้แคบลงเป็นค่าดั้งเดิม


13
บางสิ่งบางอย่างตามแนวของen.wikipedia.org/wiki/Modular_multiplicative_inverse
rwong

1
@rwong: ความคิดเห็นของคุณเป็นคำตอบที่ถูกต้องเท่านั้น
วินไคลน์

Yup วิธีการและออยเลอร์ดูเหมือนว่ามีประสิทธิภาพโดยเฉพาะอย่างยิ่งตั้งแต่ตัวประกอบของmเพียง 2 ^ 32 หรือ 2 ^ 64 รวมทั้งการยกกำลังของaโมดูโลmตรงไปตรงมา (เพียงไม่สนใจล้นมี)
MSalters

1
ฉันคิดว่าปัญหาเฉพาะคือในความเป็นจริงRational Reconstruction
MSalters

1
@MSalters: ไม่ว่าที่คุณมีr*s^-1 mod mและคุณต้องไปหาทั้งสองและr sที่นี่เรามีr*s mod mและเรารู้ทุกอย่าง rแต่
user2357112 รองรับ Monica

คำตอบ:


50

ทวีคูณโดย 1041204193

เมื่อผลของการคูณที่ไม่พอดีใน int คุณจะไม่ได้รับผลที่แน่นอน แต่คุณจะได้รับจำนวนเทียบเท่ากับผลที่แน่นอนแบบโมดูโล 2 นั่นหมายความว่าถ้าจำนวนที่คุณคูณด้วยcoprimeเป็น 2 ** 32 (ซึ่งหมายความว่าจะต้องเป็นเลขคี่) คุณสามารถคูณด้วยอินเวอร์สคูณคูณเพื่อรับหมายเลขของคุณกลับมา Wolfram Alphaหรืออัลกอริทึม Euclidean ที่ขยายเพิ่มสามารถบอกโมดูล่าผกผันการคูณแบบ 33 ของเรา 33 ว่า 32 คือ 1041204193 ดังนั้นคูณด้วย 1041204193 และคุณมี x กลับดั้งเดิม

ถ้าเราบอกว่า 60 แทน 33 เราจะไม่สามารถกู้คืนหมายเลขเดิมได้ แต่เราจะสามารถ จำกัด ให้แคบลงเหลือน้อยที่สุด ด้วยการแปลง 60 เป็น 4 * 15 ให้คำนวณอินเวอร์สของ 15 mod 2 ** 32 และคูณด้วยนั้นเราสามารถกู้คืนหมายเลขเดิมได้ 4 เท่าโดยเหลือเพียง 2 บิตลำดับสูงของแรงเดรัจฉาน Wolfram Alphaให้เรา 4008636143 สำหรับการผกผันซึ่งไม่พอดีกับ int แต่มันก็โอเค เราเพิ่งหาจำนวนที่เทียบเท่ากับ 4008636143 mod 2 ** 32 หรือบังคับให้มันอยู่ใน int เพื่อให้คอมไพเลอร์ทำเช่นนั้นกับเราและผลลัพธ์ก็จะเป็นอินเวอร์ส 15 mod 2 ** 32 ( เราจะได้รับ -286331153 )


5
โอ้เด็ก. ดังนั้นงานทั้งหมดที่คอมพิวเตอร์ของฉันได้สร้างขึ้นเสร็จแล้วโดย Euclid
v010dya

21
ฉันชอบเรื่องของความเป็นจริงในประโยคแรกของคุณ "โอ้มันคือ 1041204193 แน่นอนคุณจำไม่ได้เหรอ?" :-P
Doorknob

2
มันจะมีประโยชน์หากแสดงตัวอย่างของการทำงานนี้สำหรับตัวเลขสองสามอย่างเช่นที่ x * 33 ไม่ล้นและที่ที่มันทำ
Rob Watts

2
ใจเป่า ว้าว.
Michael Gazonda

4
คุณไม่ต้องการ Euclid หรือ WolframAlpha (แน่นอน!) เพื่อหาค่าอินเวอร์สของ 33 โมดูโล่ $ 2 ^ {32} $ ตั้งแต่ $ x = 32 = 2 ^ 5 $ เป็น nilpotent (จากคำสั่ง $ 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

สิ่งนี้อาจเหมาะกว่าสำหรับคำถามทางคณิตศาสตร์ (sic) SE โดยทั่วไปคุณต้องใช้เลขคณิตโมดูลาร์เนื่องจากการวางบิตซ้ายสุดเป็นสิ่งเดียวกัน

ฉันไม่เก่งคณิตศาสตร์เท่าคนที่อยู่ในคณิตศาสตร์ (sic) SE แต่ฉันจะพยายามตอบ

สิ่งที่เรามีตรงนี้คือจำนวนนั้นคูณด้วย 33 (3 * 11) และตัวหารร่วมกับ mod ของคุณคือ 1 นั่นก็เพราะโดยนิยามบิตในคอมพิวเตอร์เป็นพลังของสองและดังนั้น mod ของคุณคือ พลังบางอย่างของทั้งสอง

คุณจะสามารถสร้างตารางสำหรับทุกค่าก่อนหน้านี้ที่คุณคำนวณค่าต่อไปนี้ และคำถามก็คือตัวเลขต่อไปนี้ตรงกับตัวเลขก่อนหน้าหนึ่งอันเท่านั้น

ถ้าไม่ใช่ 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 เอาล่ะฉันคิดว่าตัวเลขเหล่านี้ง่ายขึ้น
v010dya

ยุติธรรมเพียงพอ 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แต่การล้นจำนวนเต็มจะทำการ%ดำเนินการให้คุณโดยอัตโนมัติเว้นแต่ว่าภาษาของคุณใช้จำนวนเต็มความแม่นยำโดยพลการ

สิ่งนี้ทำให้คุณมีชุดของตัวเลขที่อาจเป็นหมายเลขเดิม หมายเลขแรกที่พิมพ์จะเป็นหมายเลขที่จะสร้างการล้นหนึ่งรอบ ตัวเลขที่สองจะเป็นตัวเลขที่จะทำให้เกิดการล้นสองรอบ และอื่น ๆ ..

ดังนั้นถ้าคุณรู้ว่าข้อมูลของคุณดีขึ้นคุณสามารถคาดเดาได้ดีขึ้น ตัวอย่างเช่นคณิตศาสตร์นาฬิกาทั่วไป (ล้นทุก 12 นาฬิกา) มีแนวโน้มที่จะทำให้หมายเลขแรกมีแนวโน้มมากขึ้นเนื่องจากคนส่วนใหญ่มีความสนใจในสิ่งที่เกิดขึ้นในวันนี้


C # ทำงานเหมือน C กับประเภทพื้นฐาน - นั่นintคือจำนวนเต็ม 4 ไบต์ที่มีเครื่องหมายกำกับดังนั้นคำตอบของคุณยังดีแม้ว่าการบังคับเดรัจฉานจะไม่ใช่วิธีที่ดีที่สุดถ้าคุณมีอินพุตจำนวนมาก! :)
Xcelled

ใช่ฉันพยายามทำมันลงบนกระดาษที่มีกฎพีชคณิตแบบโมดูโลจากที่นี่: math.stackexchange.com/questions/346271/... แต่เมื่อผมได้ติดอยู่พยายามที่จะคิดออกและจบลงด้วยการแก้ปัญหาแรงเดรัจฉาน :)
slebetman

บทความที่น่าสนใจถึงแม้ว่าฉันจะต้องศึกษาเพิ่มเติมในเชิงลึกเพื่อให้คลิกฉันคิดว่า
Xcelled

@slebetman ดูรหัสของฉัน ดูเหมือนว่ามีเพียงคำตอบเดียวเมื่อมันมาถึงการคูณด้วย 33
v010dya

2
การแก้ไข: C intไม่รับประกันว่าจะล้อมรอบ (ดูเอกสารของคอมไพเลอร์ของคุณ) มันเป็นความจริงสำหรับประเภทที่ไม่ได้ลงนาม
Thomas Eding

1

คุณสามารถ SMT แก้ Z3 x * 33 = valueFromFileที่จะถามก็เพื่อให้คุณได้รับมอบหมายที่น่าพอใจสำหรับสูตร xมันจะกลับสมการที่ให้คุณและให้คุณค่าที่เป็นไปได้ทั้งหมด Z3 รองรับการคำนวณ bitvector ที่แน่นอนรวมถึงการคูณ

    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 ลงชื่อเข้าใช้เราจึงต้องทำอย่างถูกต้อง
    ดังนั้นเราจึง จำกัด จำนวนโดยใช้ bitwise และกับบิตของ 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:แนวคิดที่สามนั้นไม่ถูกต้องอย่างเคร่งครัด แต่ต้องทิ้งไว้ให้เข้าใจ
Int.MaxValueคือ0x7FFFFFFFแต่การล้น0xFFFFFFFF * multiplierมากที่สุดคือ
ดังนั้นข้อความที่ถูกต้องจะเป็น "การล้นเกินไม่เกิน-1 * multiplier"
สิ่งนี้ถูกต้อง แต่ไม่ใช่ทุกคนที่จะเข้าใจ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.