((a + (b & 255)) & 255) เหมือนกับ ((a + b) & 255) หรือไม่


92

ฉันกำลังเรียกดูรหัส C ++ และพบสิ่งนี้:

(a + (b & 255)) & 255

สองครั้งและทำให้ฉันรำคาญดังนั้นฉันจึงนึกถึง:

(a + b) & 255

( aและbเป็นจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ)

ฉันเขียนสคริปต์ทดสอบ (JS) อย่างรวดเร็วเพื่อยืนยันทฤษฎีของฉัน:

for (var i = 0; i < 100; i++) {
    var a = Math.ceil(Math.random() * 0xFFFF),
        b = Math.ceil(Math.random() * 0xFFFF);

    var expr1 = (a + (b & 255)) & 255,
        expr2 = (a + b) & 255;

    if (expr1 != expr2) {
        console.log("Numbers " + a + " and " + b + " mismatch!");
        break;
    }
}

แม้ว่าสคริปต์จะยืนยันสมมติฐานของฉัน (การดำเนินการทั้งสองเท่ากัน) แต่ฉันก็ยังไม่เชื่อเพราะ 1) สุ่มและ 2) ฉันไม่ใช่นักคณิตศาสตร์ฉันไม่รู้ว่าฉันกำลังทำอะไรอยู่

นอกจากนี้ขออภัยสำหรับชื่อ Lisp-y อย่าลังเลที่จะแก้ไข


4
สคริปต์นั้นเป็นภาษาอะไร ไม่Math.random()กลับจำนวนเต็มหรือในคู่ [0,1)? ฉันไม่คิดว่าบทของคุณ (ดีที่สุดเท่าที่ฉันจะบอกได้) สะท้อนปัญหาที่คุณวาง
Brick

7
รหัส c / c ++ คืออะไร? เป็นภาษาที่แตกต่างกัน
Weather Vane

14
คุณไม่สามารถจำลองพฤติกรรมที่คุณพยายามทดสอบใน JS ได้ นั่นเป็นเหตุผลว่าทำไมทุกคนถึงเลือกภาษาของคุณเท่านั้น JS ไม่ได้พิมพ์อย่างรุนแรงและคำตอบขึ้นอยู่กับประเภทของตัวแปรใน C / C ++ อย่างยิ่ง JS เป็นเรื่องไร้สาระโดยสิ้นเชิงกับคำถามที่คุณถาม
Brick

4
@WeatherVane นั่นคือรหัสเทียมที่จำเป็นโดยใช้ชื่อฟังก์ชัน Javascript คำถามของเขาเกี่ยวกับพฤติกรรมของ&และ+บนจำนวนเต็มที่ไม่ได้ลงชื่อใน C และ C ++
Barmar

11
โปรดทราบว่า "ฉันเขียนโปรแกรมทดสอบและได้รับคำตอบที่คาดหวังสำหรับอินพุตที่เป็นไปได้ทั้งหมด" ไม่ได้เป็นการรับประกันว่ามีบางอย่างที่เป็นไปตามที่คุณคาดหวัง พฤติกรรมที่ไม่ได้กำหนดอาจเป็นสิ่งที่น่ารังเกียจเช่นนั้น ให้ผลลัพธ์ที่ไม่คาดคิดหลังจากที่คุณโน้มน้าวตัวเองเสร็จแล้วว่ารหัสของคุณถูกต้อง

คำตอบ:


78

พวกเขาก็เหมือน ๆ กัน. นี่คือหลักฐาน:

ก่อนอื่นให้สังเกตตัวตน (A + B) mod C = (A mod C + B mod C) mod C

ขอย้ำปัญหาโดยการเกี่ยวกับการเป็นยืนอยู่ในa & 255 a % 256นี่เป็นความจริงเนื่องจากaไม่มีการลงนาม

ดังนั้น(a + (b & 255)) & 255เป็น(a + (b % 256)) % 256

สิ่งนี้เหมือนกับ(a % 256 + b % 256 % 256) % 256(ฉันได้ใช้ข้อมูลประจำตัวที่ระบุไว้ข้างต้นโปรดทราบว่าmodและ%เทียบเท่ากับประเภทที่ไม่ได้ลงชื่อ)

สิ่งนี้ทำให้(a % 256 + b % 256) % 256กลายเป็นเรื่องง่าย(a + b) % 256(การนำข้อมูลประจำตัวไปใช้ใหม่) จากนั้นคุณสามารถใส่ตัวดำเนินการระดับบิตกลับเพื่อให้

(a + b) & 255

เสร็จสิ้นการพิสูจน์


81
เป็นการพิสูจน์ทางคณิตศาสตร์โดยไม่สนใจความเป็นไปได้ที่จะล้น พิจารณาA=0xFFFFFFFF, B=1, C=3. ตัวตนแรกไม่ถือ. (การล้นจะไม่เป็นปัญหาสำหรับการคำนวณทางคณิตศาสตร์ที่ไม่ได้ลงนาม แต่มันเป็นสิ่งที่แตกต่างกันเล็กน้อย)
AlexD

4
อันที่จริง(a + (b & 255)) & 255ก็เหมือนกับ(a + (b % 256)) % N % 256โดยที่Nค่าหนึ่งใหญ่กว่าค่าสูงสุดที่ไม่ได้ลงนาม (สูตรหลังหมายถึงการตีความเป็นเลขคณิตของจำนวนเต็มทางคณิตศาสตร์)

17
การพิสูจน์ทางคณิตศาสตร์เช่นนี้ไม่เหมาะสมสำหรับการพิสูจน์พฤติกรรมของจำนวนเต็มบนสถาปัตยกรรมคอมพิวเตอร์
Jack Aidley

25
@JackAidley: เหมาะสมเมื่อทำอย่างถูกต้อง (ซึ่งไม่ได้เป็นเพราะละเลยที่จะพิจารณาว่าล้น)

3
@ Shaz: นั่นเป็นความจริงของสคริปต์ทดสอบ แต่ไม่ใช่ส่วนหนึ่งของคำถามที่ถาม

21

ในการเพิ่มตำแหน่งการลบและการคูณของตัวเลขที่ไม่ได้ลงชื่อเพื่อให้ได้ผลลัพธ์ที่ไม่ได้ลงนามตัวเลขที่มีนัยสำคัญมากขึ้นของอินพุตจะไม่ส่งผลต่อตัวเลขที่มีนัยสำคัญน้อยของผลลัพธ์ สิ่งนี้ใช้กับเลขคณิตไบนารีเท่า ๆ กับเลขคณิตทศนิยม นอกจากนี้ยังใช้กับเลขคณิตที่มีการลงนาม "twos complement" ด้วย แต่ไม่ใช้กับเลขคณิตที่ลงนามขนาดเครื่องหมาย

อย่างไรก็ตามเราต้องระมัดระวังในการใช้กฎจากเลขคณิตไบนารีและนำไปใช้กับ C (ฉันเชื่อ C ++ มีกฎเดียวกันกับ C ในสิ่งนี้ แต่ฉันไม่แน่ใจ 100%) เนื่องจากเลขคณิต C มีกฎอาร์เคนบางอย่างที่สามารถเดินทางเราได้ ขึ้น. เลขคณิตที่ไม่ได้ลงนามใน C เป็นไปตามกฎการตัดกันแบบไบนารีอย่างง่าย แต่การล้นเลขคณิตที่ลงนามเป็นพฤติกรรมที่ไม่ได้กำหนด แย่กว่านั้นในบางสถานการณ์ C จะ "เลื่อนระดับ" ประเภทที่ไม่ได้ลงนามเป็น (ลงชื่อ) int โดยอัตโนมัติ

พฤติกรรมที่ไม่ได้กำหนดไว้ใน C อาจไม่เป็นอันตรายโดยเฉพาะอย่างยิ่ง คอมไพเลอร์ใบ้ (หรือคอมไพเลอร์ในระดับการเพิ่มประสิทธิภาพต่ำ) มีแนวโน้มที่จะทำในสิ่งที่คุณคาดหวังตามความเข้าใจของคุณเกี่ยวกับเลขคณิตไบนารีในขณะที่คอมไพเลอร์การเพิ่มประสิทธิภาพอาจทำลายโค้ดของคุณด้วยวิธีแปลก ๆ


ดังนั้นการกลับไปที่สูตรในคำถามความเท่าเทียมกันขึ้นอยู่กับชนิดตัวถูกดำเนินการ

หากเป็นจำนวนเต็มที่ไม่ได้ลงนามซึ่งมีขนาดมากกว่าหรือเท่ากับขนาดของintลักษณะการทำงานล้นของตัวดำเนินการการบวกจะถูกกำหนดไว้อย่างดีว่าเป็นการตัดวงจรไบนารีอย่างง่าย ไม่ว่าเราจะปิดบัง 24 บิตสูงของตัวถูกดำเนินการหนึ่งตัวหรือไม่ก่อนที่การดำเนินการเพิ่มจะไม่มีผลกระทบกับบิตต่ำของผลลัพธ์

หากพวกเขาเป็นจำนวนเต็มไม่ได้ลงนามที่มีขนาดน้อยกว่าintแล้วพวกเขาก็จะได้รับการเลื่อนตำแหน่งให้เป็น int(ลงนาม) การล้นของจำนวนเต็มที่ลงนามเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ แต่อย่างน้อยในทุกแพลตฟอร์มฉันพบความแตกต่างของขนาดระหว่างประเภทจำนวนเต็มที่แตกต่างกันมีขนาดใหญ่พอที่การเพิ่มค่าที่เลื่อนระดับสองค่าเพียงครั้งเดียวจะไม่ทำให้เกิดการล้น เราสามารถกลับไปใช้อาร์กิวเมนต์เลขคณิตไบนารีได้อีกครั้งเพื่อให้ถือว่างบเทียบเท่า

หากพวกเขาเป็นจำนวนเต็มที่ลงนามซึ่งมีขนาดน้อยกว่า int การล้นอีกครั้งจะไม่สามารถเกิดขึ้นได้และในการใช้งานแบบเสริมสองเราสามารถอาศัยอาร์กิวเมนต์เลขคณิตไบนารีมาตรฐานเพื่อบอกว่ามีความเท่าเทียมกัน ในการเข้าสู่ระบบขนาดหรือส่วนเสริมการใช้งานพวกเขาจะไม่เทียบเท่า

OTOH ถ้าaและbได้รับการลงนามจำนวนเต็มซึ่งมีขนาดมากกว่าหรือเท่ากับขนาดของ int แล้วแม้แต่ในการใช้งานเสริมสองครั้งก็มีบางกรณีที่คำสั่งหนึ่งจะได้รับการกำหนดไว้อย่างดีในขณะที่อีกคำสั่งหนึ่งจะเป็นพฤติกรรมที่ไม่ได้กำหนด


20

แทรก: สำหรับการได้รับการรับรองa & 255 == a % 256a

ไม่ได้ลงนามaสามารถเขียนใหม่เป็นm * 0x100 + bบางส่วนที่ไม่มีการลงชื่อm, b, ,0 <= b < 0xff มันดังมาจากทั้งสองคำจำกัดความว่า0 <= m <= 0xffffffa & 255 == b == a % 256

นอกจากนี้เราต้องการ:

  • คุณสมบัติการกระจาย: (a + b) mod n = [(a mod n) + (b mod n)] mod n
  • ความหมายของการเพิ่มที่ไม่ได้ลงนามทางคณิตศาสตร์: (a + b) ==> (a + b) % (2 ^ 32)

ดังนั้น:

(a + (b & 255)) & 255 = ((a + (b & 255)) % (2^32)) & 255      // def'n of addition
                      = ((a + (b % 256)) % (2^32)) % 256      // lemma
                      = (a + (b % 256)) % 256                 // because 256 divides (2^32)
                      = ((a % 256) + (b % 256 % 256)) % 256   // Distributive
                      = ((a % 256) + (b % 256)) % 256         // a mod n mod n = a mod n
                      = (a + b) % 256                         // Distributive again
                      = (a + b) & 255                         // lemma

ใช่มันเป็นความจริง สำหรับจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ


จำนวนเต็มประเภทอื่น ๆ ล่ะ?

  • สำหรับจำนวนเต็มไม่ได้ลงนาม 64 บิตทั้งหมดข้างต้นมีผลบังคับใช้ก็เช่นกันเพียงแค่ทำหน้าที่แทนสำหรับ2^642^32
  • สำหรับจำนวนเต็ม 8 และ 16 บิตที่ไม่ได้ลงชื่อการเพิ่มนั้นเกี่ยวข้องกับการเลื่อนintระดับ สิ่งนี้intจะไม่ล้นหรือเป็นลบในการดำเนินการใด ๆ ดังนั้นการดำเนินการทั้งหมดยังคงใช้ได้
  • สำหรับจำนวนเต็มที่เซ็นชื่อถ้าอย่างใดอย่างหนึ่งa+bหรือa+(b&255)มากเกินไปจะเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ ดังนั้นความเท่าเทียมกันจึงไม่สามารถระงับได้ - มีบางกรณีที่(a+b)&255เป็นพฤติกรรมที่ไม่ได้กำหนด แต่(a+(b&255))&255ไม่ใช่

17

ใช่(a + b) & 255สบายดี

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


ข้างต้นไม่เป็นความจริงเสมอไปมาตรฐาน C ++ อนุญาตให้มีการใช้งานที่จะทำลายสิ่งนี้

Deathstation 9000 ดังกล่าว: - )จะต้องใช้ 33 บิตintหาก OP หมายถึงunsigned short"จำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อ" หากunsigned intมีความหมาย DS9K จะต้องใช้ 32 บิตintและ 32 บิตที่unsigned intมีช่องว่างภายใน (จำนวนเต็มที่ไม่ได้ลงนามจะต้องมีขนาดเดียวกับคู่ที่ลงนามตาม§3.9.1 / 3 และอนุญาตให้ใช้บิตรองใน§3.9.1 / 1) การผสมขนาดและบิตช่องว่างอื่น ๆ ก็ใช้ได้เช่นกัน

เท่าที่ฉันสามารถบอกได้นี่เป็นวิธีเดียวที่จะทำลายมันเพราะ:

  • การแทนค่าจำนวนเต็มต้องใช้รูปแบบการเข้ารหัส "ไบนารีล้วนๆ" (§3.9.1 / 7 และเชิงอรรถ) บิตทั้งหมดยกเว้นบิตช่องว่างและบิตเครื่องหมายต้องมีค่า 2 n
  • การส่งเสริม int จะได้รับอนุญาตก็ต่อเมื่อintสามารถแทนค่าทั้งหมดของประเภทแหล่งที่มา (§4.5 / 1) ดังนั้นintต้องมีอย่างน้อย 32 บิตที่มีส่วนในค่าบวกบิตเครื่องหมาย
  • intไม่สามารถมีบิตค่ามากขึ้น (ไม่นับบิตเครื่องหมาย) มากกว่า 32 เพราะที่อื่นนอกจากนี้ไม่สามารถล้น

2
มีการดำเนินการอื่น ๆ อีกมากมายนอกเหนือจากการเพิ่มที่ขยะในบิตสูงไม่ส่งผลกระทบต่อผลลัพธ์ในบิตต่ำที่คุณสนใจดูคำถามและคำตอบเกี่ยวกับส่วนเติมเต็มของ 2ซึ่งใช้ x86 asm เป็นกรณีการใช้งาน แต่ยังใช้กับ เลขฐานสองที่ไม่ได้ลงชื่อในทุกสถานการณ์
Peter Cordes

2
แม้ว่าทุกคนจะมีสิทธิ์ลงคะแนนโดยไม่เปิดเผยตัวตน แต่ฉันก็มักจะขอบคุณความคิดเห็นที่เป็นโอกาสในการเรียนรู้
Alain

2
นี่เป็นคำตอบ / ข้อโต้แย้งที่ง่ายที่สุดในการทำความเข้าใจ IMO การดำเนินการ / ยืมในการบวก / การลบจะแพร่กระจายจากบิตต่ำไปยังบิตสูง (ขวาไปซ้าย) ในรูปแบบไบนารีเช่นเดียวกับในฐานสิบ IDK ทำไมบางคนถึงโหวตลงคะแนนนี้
Peter Cordes

1
@Bathsheba: CHAR_BIT ไม่จำเป็นต้องเป็น 8 แต่ประเภทที่ไม่ได้ลงชื่อใน C และ C ++ จะต้องทำงานเป็นเลขฐานสองไบนารีปกติของความกว้างบิต ผมคิดว่าต้องว่า UINT_MAX 2^N-1คือ (N อาจไม่จำเป็นต้องเป็นจำนวนเต็มของ CHAR_BIT ฉันลืมไป แต่ฉันค่อนข้างแน่ใจว่ามาตรฐานต้องการให้การห่อหุ้มเกิดขึ้นโมดูโลพลังบางอย่างของ 2) ฉันคิดว่าวิธีเดียวที่คุณจะได้รับความแปลกคือผ่านการโปรโมตไปยัง a แบบเซ็นที่กว้างพอที่จะถือaหรือbแต่ไม่กว้างพอที่จะถือได้a+bในทุกกรณี
Peter Cordes

2
@ บา ธ เชบา: ใช่โชคดีที่ C-as-portable-assembly-language ใช้งานได้กับประเภทที่ไม่ได้ลงนามเป็นส่วนใหญ่ แม้แต่การใช้งาน C ที่ไม่เป็นมิตรโดยเจตนาก็สามารถทำลายสิ่งนี้ได้ เป็นประเภทที่เซ็นชื่อเท่านั้นที่สิ่งต่างๆน่ากลัวสำหรับบิตแฮ็กแบบพกพาอย่างแท้จริงใน C และ Deathstation 9000 สามารถทำลายรหัสของคุณได้
Peter Cordes

14

คุณมีคำตอบที่ชาญฉลาดอยู่แล้ว: เลขคณิตที่ไม่ได้ลงชื่อเป็นเลขคณิตแบบโมดูโลดังนั้นผลลัพธ์จะถือคุณสามารถพิสูจน์ได้ทางคณิตศาสตร์ ...


สิ่งที่ยอดเยี่ยมอย่างหนึ่งเกี่ยวกับคอมพิวเตอร์คือคอมพิวเตอร์ทำงานเร็ว อันที่จริงมันเร็วมากจนการแจกแจงชุดค่าผสม 32 บิตที่ถูกต้องทั้งหมดเป็นไปได้ในระยะเวลาที่เหมาะสม (อย่าลองด้วย 64 บิต)

ดังนั้นในกรณีของคุณโดยส่วนตัวแล้วฉันชอบโยนมันใส่คอมพิวเตอร์ ฉันใช้เวลาน้อยกว่าในการโน้มน้าวตัวเองว่าโปรแกรมนั้นถูกต้องกว่าที่จะโน้มน้าวตัวเองได้ดีกว่าการพิสูจน์ทางคณิตศาสตร์นั้นถูกต้องและฉันไม่ได้ดูแลรายละเอียดในข้อกำหนด1 :

#include <iostream>
#include <limits>

int main() {
    std::uint64_t const MAX = std::uint64_t(1) << 32;
    for (std::uint64_t i = 0; i < MAX; ++i) {
        for (std::uint64_t j = 0; j < MAX; ++j) {
            std::uint32_t const a = static_cast<std::uint32_t>(i);
            std::uint32_t const b = static_cast<std::uint32_t>(j);

            auto const champion = (a + (b & 255)) & 255;
            auto const challenger = (a + b) & 255;

            if (champion == challenger) { continue; }

            std::cout << "a: " << a << ", b: " << b << ", champion: " << champion << ", challenger: " << challenger << "\n";
            return 1;
        }
    }

    std::cout << "Equality holds\n";
    return 0;
}

สิ่งนี้จะแจกแจงค่าที่เป็นไปได้ทั้งหมดของaและbในช่องว่าง 32 บิตและตรวจสอบว่ามีความเท่าเทียมกันหรือไม่ หากไม่เป็นเช่นนั้นระบบจะพิมพ์เคสที่ใช้ไม่ได้ผลซึ่งคุณสามารถใช้เป็นการตรวจสอบความสมบูรณ์ได้

และตามเสียงดังกราว : ความเท่าเทียมกันถือ

นอกจากนี้เนื่องจากกฎเลขคณิตเป็นแบบไม่เชื่อเรื่องพระเจ้าแบบความกว้างบิต (สูงกว่าintความกว้างบิต) ความเท่าเทียมกันนี้จะเก็บไว้สำหรับประเภทจำนวนเต็ม 32 บิตหรือมากกว่าที่ไม่ได้ลงนามรวมถึง 64 บิตและ 128 บิต

หมายเหตุ: คอมไพลเลอร์จะระบุรูปแบบ 64 บิตทั้งหมดในกรอบเวลาที่เหมาะสมได้อย่างไร มันไม่สามารถ ลูปถูกปรับให้เหมาะสม มิฉะนั้นเราทุกคนจะเสียชีวิตก่อนที่การประหารชีวิตจะสิ้นสุดลง


ตอนแรกฉันพิสูจน์แล้วสำหรับจำนวนเต็ม 16 บิตที่ไม่ได้ลงชื่อ น่าเสียดายที่ C ++ เป็นภาษาที่บ้าจำนวนเต็มขนาดเล็ก (bitwidths ขนาดเล็กกว่าint) intจะถูกแปลงแรกที่จะ

#include <iostream>

int main() {
    unsigned const MAX = 65536;
    for (unsigned i = 0; i < MAX; ++i) {
        for (unsigned j = 0; j < MAX; ++j) {
            std::uint16_t const a = static_cast<std::uint16_t>(i);
            std::uint16_t const b = static_cast<std::uint16_t>(j);

            auto const champion = (a + (b & 255)) & 255;
            auto const challenger = (a + b) & 255;

            if (champion == challenger) { continue; }

            std::cout << "a: " << a << ", b: " << b << ", champion: "
                      << champion << ", challenger: " << challenger << "\n";
            return 1;
        }
    }

    std::cout << "Equality holds\n";
    return 0;
}

และอีกครั้งตามเสียงดังกราว : ความเท่าเทียมกันถือ

เอาล่ะ :)


1 แน่นอนว่าหากโปรแกรมมีการกระตุ้นพฤติกรรมที่ไม่ได้กำหนดโดยไม่ได้ตั้งใจก็จะไม่สามารถพิสูจน์ได้มากนัก


1
คุณบอกว่ามันง่ายที่จะทำด้วยค่า 32 บิต แต่ใช้จริง 16 บิต ... : D
Willi Mentzel

1
@ WilliMentzel: นั่นเป็นคำพูดที่น่าสนใจ ตอนแรกฉันอยากจะบอกว่าถ้ามันทำงานกับ 16 บิตมันจะทำงานเหมือนกันกับ 32 บิต 64 บิตและ 128 บิตเนื่องจากมาตรฐานไม่มีพฤติกรรมเฉพาะสำหรับความกว้างบิตที่แตกต่างกัน ... อย่างไรก็ตามฉันจำได้ว่ามันทำได้จริง สำหรับความกว้างบิตที่เล็กกว่าของintจำนวนเต็มขนาดเล็กจะถูกแปลงเป็นint(กฎประหลาด) ดังนั้นฉันต้องทำการสาธิตด้วย 32 บิต (และหลังจากนั้นจะขยายเป็น 64 บิต, 128 บิต, ... )
Matthieu M.

2
เนื่องจากคุณไม่สามารถประเมินทั้งหมดได้ (4294967296 - 1) * (4294967296 - 1) ผลลัพธ์ที่เป็นไปได้คุณจึงลดลงบ้างไหม? ฉันคิดว่า MAX ควรจะเป็น (4294967296 - 1) ถ้าคุณไปทางนั้น แต่มันจะไม่จบภายในชีวิตของเราอย่างที่คุณพูด ... ดังนั้นเราจึงไม่สามารถแสดงความเท่าเทียมกันในการทดลองได้อย่างน้อยก็ไม่ใช่ในหนึ่งเดียวเหมือนคุณ อธิบาย.
Willi Mentzel

1
การทดสอบสิ่งนี้ในการใช้งานส่วนเสริมของ one 2 ไม่ได้พิสูจน์ว่าเป็นแบบพกพาสำหรับขนาดการลงชื่อหรือส่วนเสริมของตัวเองด้วยความกว้างประเภท Deathstation 9000 เช่นประเภทที่ไม่ได้ลงนามแบบแคบสามารถเลื่อนระดับเป็น 17 บิตintซึ่งสามารถแสดงทุกสิ่งที่เป็นไปได้uint16_tแต่a+bจะล้นได้ที่ไหน นั่นเป็นเพียงปัญหาสำหรับประเภทที่ไม่ได้ลงชื่อแคบกว่าint; C กำหนดให้unsignedประเภทเป็นจำนวนเต็มไบนารีดังนั้นการสรุปจะเกิดขึ้นโมดูโลโดยมีกำลัง 2
Peter Cordes

1
เห็นด้วยกับการที่ C พกพาสะดวกเกินไปสำหรับตัวมันเอง มันจะเป็นจริงๆดีถ้าพวกเขาต้องการสร้างมาตรฐานในวันที่ 2 ของส่วนประกอบคณิตศาสตร์ขวากะสำหรับการลงนามและวิธีการที่จะไม่ลงนามในความหมายทางคณิตศาสตร์กับการตัดแทนความหมายไม่ได้กำหนดพฤติกรรมสำหรับกรณีที่เมื่อคุณต้องการห่อ จากนั้น C อาจมีประโยชน์อีกครั้งในฐานะแอสเซมเบลอร์แบบพกพาแทนที่จะเป็นสนามที่วางทุ่นระเบิดต้องขอบคุณคอมไพเลอร์ที่ปรับแต่งให้ทันสมัยซึ่งทำให้ไม่ปลอดภัยที่จะทิ้งพฤติกรรมที่ไม่ได้กำหนดไว้ (อย่างน้อยก็สำหรับแพลตฟอร์มเป้าหมายของคุณพฤติกรรมที่ไม่ได้กำหนดเฉพาะในการใช้งาน Deathstation 9000 ก็โอเคตามที่คุณต้องการ บ่งชี้).
Peter Cordes

4

คำตอบด่วนคือนิพจน์ทั้งสองเทียบเท่ากัน

  • เนื่องจากaและbเป็นจำนวนเต็ม 32 บิตที่ไม่ได้ลงชื่อผลลัพธ์จะเหมือนกันแม้ในกรณีที่ล้น การคำนวณทางคณิตศาสตร์ที่ไม่ได้ลงนามรับประกันสิ่งนี้: ผลลัพธ์ที่ไม่สามารถแสดงโดยประเภทจำนวนเต็มที่ไม่ได้ลงนามที่เป็นผลลัพธ์จะถูกลดโมดูโลจำนวนที่มากกว่าค่ามากที่สุดค่าหนึ่งที่สามารถแสดงโดยประเภทผลลัพธ์ได้

คำตอบยาว ๆ คือไม่มีแพลตฟอร์มใดที่เป็นที่รู้จักซึ่งนิพจน์เหล่านี้จะแตกต่างกันไป แต่ Standard ไม่รับประกันเนื่องจากกฎของการส่งเสริมการขายแบบอินทิกรัล

  • ถ้าชนิดของaและb(ไม่ได้ลงนาม 32 จำนวนเต็มบิต) มีอันดับสูงกว่าintการคำนวณที่มีการดำเนินการตามที่ได้รับการรับรอง, โมดูโล 2 32และอัตราผลตอบแทนผลที่กำหนดไว้เหมือนกันสำหรับการแสดงออกทั้งค่าทั้งหมดของและab

  • ในทางกลับกันถ้าประเภทของaและbมีขนาดเล็กกว่าintทั้งสองจะได้รับการเลื่อนระดับintและการคำนวณจะดำเนินการโดยใช้เลขคณิตที่มีลายเซ็นซึ่งส่วนเกินจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด

    • หากintมีบิตค่าอย่างน้อย 33 นิพจน์ทั้งสองนิพจน์ข้างต้นไม่สามารถล้นดังนั้นผลลัพธ์จึงถูกกำหนดอย่างสมบูรณ์และมีค่าเดียวกันสำหรับทั้งสองนิพจน์

    • หากintมีบิตค่า 32 บิตการคำนวณอาจล้นสำหรับทั้งสองนิพจน์ตัวอย่างเช่นค่าa=0xFFFFFFFFและb=1อาจทำให้เกิดการล้นในทั้งสองนิพจน์ ((a & 255) + (b & 255)) & 255เพื่อหลีกเลี่ยงการนี้คุณจะต้องเขียน

  • ข่าวดีก็คือไม่มีแพลตฟอร์มดังกล่าว1 .


1อย่างแม่นยำยิ่งกว่านั้นไม่มีแพลตฟอร์มจริงดังกล่าว แต่เราสามารถกำหนดค่าDS9Kเพื่อแสดงพฤติกรรมดังกล่าวและยังคงเป็นไปตามมาตรฐาน C


3
subbullet ที่ 2 ของคุณต้อง (1) aมีขนาดเล็กกว่าint(2) intมี 32 บิตค่า a=0xFFFFFFFF(3) สิ่งเหล่านี้ไม่สามารถเป็นจริงได้ทั้งหมด
Barry

1
@ แบร์รี่: กรณีหนึ่งที่ดูเหมือนจะตรงตามข้อกำหนดคือ 33 บิตintโดยที่มีบิตค่า 32 บิตและบิตเครื่องหมายหนึ่งบิต
Ben Voigt

2

เหมือนกันสมมติว่าไม่มีล้น ทั้งสองเวอร์ชันไม่ได้รับการป้องกันอย่างแท้จริงจากการล้น แต่เวอร์ชันสองและเวอร์ชันนั้นทนทานกว่า ฉันไม่ทราบถึงระบบที่มีปัญหาล้นในกรณีนี้ แต่ฉันเห็นผู้เขียนทำเช่นนี้ในกรณีที่มี


1
สหกรณ์ระบุ(a และ b เป็น 32 บิตจำนวนเต็มไม่ได้ลงนาม) เว้นแต่intจะกว้าง 33 บิตผลลัพธ์จะเหมือนกันแม้ในกรณีที่ล้น การคำนวณทางคณิตศาสตร์ที่ไม่ได้ลงนามรับประกันสิ่งนี้: ผลลัพธ์ที่ไม่สามารถแสดงโดยประเภทจำนวนเต็มที่ไม่ได้ลงนามที่เป็นผลลัพธ์จะถูกลดโมดูโลจำนวนที่มากกว่าค่ามากที่สุดค่าหนึ่งที่สามารถแสดงโดยประเภทผลลัพธ์ได้
chqrlie

2

ใช่คุณสามารถพิสูจน์ได้ด้วยเลขคณิต แต่มีคำตอบที่เข้าใจง่ายกว่า

เมื่อเพิ่มทุกบิตจะมีอิทธิพลมากกว่าตัวมันเอง ไม่สำคัญน้อยกว่า

ดังนั้นสิ่งที่คุณทำกับบิตที่สูงกว่าก่อนการบวกจะไม่เปลี่ยนแปลงผลลัพธ์ตราบใดที่คุณเก็บบิตที่มีความสำคัญน้อยกว่าบิตต่ำสุดที่แก้ไขเท่านั้น


0

หลักฐานเป็นเรื่องเล็กน้อยและปล่อยให้เป็นแบบฝึกหัดสำหรับผู้อ่าน

แต่ในการทำให้สิ่งนี้เป็นคำตอบที่ถูกต้องตามความเป็นจริงโค้ดบรรทัดแรกของคุณบอกว่าใช้ 8 บิตสุดท้ายของb** (บิตที่สูงกว่าทั้งหมดของการbตั้งค่าเป็นศูนย์) และเพิ่มสิ่งนี้เข้าไปaจากนั้นใช้เวลาเพียง 8 บิตสุดท้ายของการตั้งค่าผลลัพธ์ทั้งหมดที่สูงกว่า บิตเป็นศูนย์

บรรทัดที่สองระบุว่า add aand band take 8 bits last โดยบิตที่สูงกว่าทั้งหมดเป็นศูนย์

เฉพาะ 8 บิตสุดท้ายเท่านั้นที่มีความสำคัญในผลลัพธ์ ดังนั้นเฉพาะ 8 บิตสุดท้ายเท่านั้นที่มีนัยสำคัญในอินพุต

** 8 บิตสุดท้าย = 8 LSB

นอกจากนี้ยังเป็นที่น่าสนใจที่จะทราบว่าผลลัพธ์จะเทียบเท่ากับ

char a = something;
char b = something;
return (unsigned int)(a + b);

ดังที่กล่าวมามีเพียง 8 LSB เท่านั้นที่มีความสำคัญ แต่ผลลัพธ์ที่ได้คือunsigned intกับบิตอื่น ๆ ทั้งหมดเป็นศูนย์ a + bจะล้นผลิตผลที่คาดหวัง


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