ทำไม (a% 256) แตกต่างจาก (a & 0xFF)


145

ฉันมักจะสันนิษฐานว่าเมื่อทำเพิ่มประสิทธิภาพธรรมชาติจะใช้การดำเนินการที่มีประสิทธิภาพค่าที่เหมาะสมเช่นถ้าผมเขียน(a % 256)(a & 0xFF)

เมื่อทำการทดสอบกับคอมไพเลอร์ explorer gcc-6.2 (-O3):

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

และเมื่อลองรหัสอื่น:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

ดูเหมือนว่าฉันกำลังพลาดอะไรบางอย่างไปหมด ความคิดใด ๆ


64
0xFF คือ 255 ไม่ใช่ 256
Rishikesh Raje

186
@RishikeshRaje: งั้นเหรอ? %ไม่&เช่นกัน
usr2564301

27
@RishikeshRaje: ฉันแน่ใจว่า OP ตระหนักดีมาก พวกเขากำลังใช้กับการดำเนินงานที่แตกต่างกัน
ไชโยและ hth - Alf

28
ออกจากดอกเบี้ยที่คุณได้รับผลลัพธ์ที่ดีกว่าถ้าnumเป็นunsigned?
Bathsheba

20
@RishikeshRaje Bitwise และ 0xFF เทียบเท่ากับ modulo 2 ^ 8 สำหรับจำนวนเต็มที่ไม่ได้ลงนาม
2501

คำตอบ:


230

มันไม่เหมือนกัน ลองnum = -79และคุณจะได้รับผลลัพธ์ที่แตกต่างจากการดำเนินการทั้งสอง (-79) % 256 = -79ในขณะที่(-79) & 0xffเป็นจำนวนบวก

การใช้unsigned intการดำเนินการเหมือนกันและรหัสอาจจะเหมือนกัน

PS- มีคนแสดงความคิดเห็น

พวกเขาไม่ควรจะเหมือนกันถูกกำหนดให้เป็นa % ba - b * floor (a / b)

นั่นไม่ใช่วิธีที่กำหนดไว้ใน C, C ++, Objective-C (เช่นทุกภาษาที่มีรหัสในคำถามรวบรวม)


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Martijn Pieters

52

คำตอบสั้น ๆ

-1 % 256อัตราผลตอบแทน-1และไม่ได้ซึ่งเป็น255 -1 & 0xFFดังนั้นการเพิ่มประสิทธิภาพจะไม่ถูกต้อง

คำตอบที่ยาว

C ++ มีการประชุมที่(a/b)*b + a%b == aดูเหมือนเป็นธรรมชาติ a/bส่งคืนผลลัพธ์เลขคณิตเสมอโดยไม่มีส่วนที่เป็นเศษส่วน (ตัดเหลือ 0) ผลที่ตามมาa%bก็คือมีสัญญาณเดียวกันกับaหรือเป็น 0

การแบ่ง-1/256ผลตอบแทน0และด้วยเหตุนี้-1%256จะต้องเป็น-1ไปตามเงื่อนไขข้างต้น ( (-1%256)*256 + -1%256 == -1) นี้จะเห็นได้ชัดจากการที่แตกต่างกันซึ่งเป็น-1&0xFF 0xFFดังนั้นคอมไพเลอร์ไม่สามารถปรับวิธีที่คุณต้องการ

ส่วนที่เกี่ยวข้องในมาตรฐาน C ++ [expr.mul §4] ณสถานะN4606 :

สำหรับโอเปอแรนด์หนึ่งตัวดำเนิน/การหาค่าผลหารพีชคณิตด้วยส่วนเศษส่วนใด ๆ หากความฉลาดทางสามารถa/bแทนได้ในประเภทของผลลัพธ์(a/b)*b + a%bจะเท่ากับa[... ]

เปิดใช้งานการเพิ่มประสิทธิภาพ

อย่างไรก็ตามการใช้unsignedประเภทการปรับให้เหมาะสมจะสมบูรณ์ถูกต้องเป็นไปตามอนุสัญญาข้างต้น:

unsigned(-1)%256 == 0xFF

ดูสิ่งนี้ด้วย

ภาษาอื่น ๆ

สิ่งนี้ได้รับการจัดการแตกต่างกันมากในภาษาการเขียนโปรแกรมต่าง ๆ เนื่องจากคุณสามารถค้นหาบนWikipediaได้


50

ตั้งแต่ C ++ 11 num % 256จะต้องไม่เป็นค่าบวกหากnumเป็นลบ

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

มันจะเป็นเรื่องที่แตกต่างถ้าnumในกรณีของคุณunsigned: วันนี้ฉันเกือบจะคาดหวังว่าคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพที่คุณอ้างถึง


6
เกือบ แต่ไม่มาก หากnumเป็นลบแสดงว่าnum % 256เป็นศูนย์หรือลบ (aka ไม่ใช่บวก)
Nayuki

5
IMO ตัวใดที่ผิดพลาดในมาตรฐาน: การดำเนินการแบบโมดูโลทางคณิตศาสตร์ควรใช้สัญลักษณ์ของตัวหาร 256 ในกรณีนี้ เพื่อที่จะเข้าใจว่าทำไมพิจารณาว่า(-250+256)%256==6แต่(-250%256)+(256%256)จะต้องเป็นไปตามมาตรฐาน "ไม่ใช่ในเชิงบวก" 6และดังนั้นจึงไม่ การทำลายความสัมพันธ์เช่นนั้นมีผลข้างเคียงในชีวิตจริง: ตัวอย่างเช่นเมื่อคำนวณการแสดงภาพ "ย่อ / ขยาย" ในพิกัดจำนวนเต็มเราต้องเปลี่ยนภาพให้มีพิกัดที่ไม่เป็นลบทั้งหมด
Michael

2
@Michael Modulus ไม่เคยมีการแจกจ่ายเกินนอกจากนี้ ("เชื่อมโยง" เป็นชื่อที่ไม่ถูกต้องสำหรับคุณสมบัตินี้!) แม้ว่าคุณจะทำตามคำจำกัดความทางคณิตศาสตร์ของจดหมาย ยกตัวอย่างเช่นแต่(128+128)%256==0 (128%256)+(128%256)==256อาจมีการคัดค้านที่ดีสำหรับพฤติกรรมที่ระบุ แต่ไม่ชัดเจนสำหรับฉันว่าเป็นสิ่งที่คุณพูด
Daniel Wagner

1
@DanielWagner คุณพูดถูกฉันคิดผิดด้วย "associative" อย่างไรก็ตามถ้ามีใครเก็บเครื่องหมายของตัวหารและคำนวณทุกอย่างในเลขคณิตแบบแยกส่วนสมบัติการกระจายจะถือ; 256==0ในตัวอย่างของคุณคุณจะต้อง ที่สำคัญคือการได้ว่าNค่าที่เป็นไปในแบบโมดูโลNคณิตศาสตร์ซึ่งเป็นเพียงเป็นไปได้ถ้าผลทั้งหมดที่อยู่ในช่วงไม่0,...,(N-1) -(N-1),...,(N-1)
Michael

6
@Michael: ยกเว้น% ไม่ใช่ตัวดำเนินการ modulo มันเป็นตัวดำเนินการส่วนที่เหลือ
Joren

11

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

sarคำสั่งเสียงที่ฉันชอบ "เปลี่ยนสิทธิทางคณิตศาสตร์" เติมขึ้นบิตประดับด้วยค่าบิตเครื่องหมาย


0

การพูดทางคณิตศาสตร์โมดูโลถูกกำหนดให้เป็นดังต่อไปนี้:

a% b = a - b * floor (a / b)

นี่ตรงนี้ควรจะเคลียร์ให้คุณ เราสามารถกำจัด floor สำหรับจำนวนเต็มได้เนื่องจากการหารจำนวนเต็มเทียบเท่ากับ floor (a / b) อย่างไรก็ตามหากคอมไพเลอร์ใช้กลอุบายทั่วไปอย่างที่คุณแนะนำมันจะต้องใช้กับ a และ all b น่าเสียดายที่นี่ไม่ใช่กรณี การพูดทางคณิตศาสตร์กลอุบายของคุณถูกต้อง 100% สำหรับจำนวนเต็มที่ไม่ได้ลงนาม (ฉันเห็นคำตอบที่ระบุจำนวนเต็มลง แต่ฉันสามารถยืนยันหรือปฏิเสธได้ว่า -a% b ควรเป็นค่าบวก) อย่างไรก็ตามคุณสามารถทำเคล็ดลับนี้สำหรับ b ทั้งหมดได้หรือไม่? อาจจะไม่. นั่นเป็นสาเหตุที่คอมไพเลอร์ไม่ทำ ท้ายที่สุดถ้าโมดูโลถูกเขียนอย่างง่าย ๆ เป็นการดำเนินการระดับบิตหนึ่งเราก็จะเพิ่มวงจรโมดูโลเพื่อเพิ่มและดำเนินการอื่น ๆ


4
ฉันคิดว่าคุณสับสน "ชั้น" กับ "ตัด" คอมพิวเตอร์ยุคแรก ๆ ใช้การตัดทอนเพราะมันง่ายต่อการคำนวณมากกว่าการแบ่งพื้นแม้ในกรณีที่สิ่งต่าง ๆ เกิดขึ้นเพื่อแบ่งเท่า ๆ กัน ฉันเคยเห็นบางกรณีที่แผนกที่ถูกตัดทอนมีประโยชน์มากกว่าแผนกที่เป็นพื้น แต่หลาย ๆ ภาษาใช้ภาษาของ FORTRAN ในการใช้ส่วนที่ถูกตัดทอน
supercat

@supercat การพูดทางคณิตศาสตร์ชั้นถูกตัดทอน พวกเขาทั้งสองมีผลเหมือนกัน พวกเขาอาจไม่ได้ใช้งานเหมือนกันในคอมพิวเตอร์ แต่พวกเขาทำสิ่งเดียวกัน
user64742

5
@TheGreatDuck: พวกเขาไม่เหมือนกันสำหรับตัวเลขลบ พื้น-2.3เป็น-3ในขณะที่ถ้าคุณตัดทอนให้เป็นจำนวนเต็มคุณจะได้รับ-2.3 -2ดูen.wikipedia.org/wiki/Truncation "สำหรับการลบตัวเลขการปัดเศษไม่ได้ปัดในทิศทางเดียวกันกับฟังก์ชั่นพื้น" และพฤติกรรมของ%ตัวเลขที่เป็นลบนั้นเป็นเหตุผลอย่างแม่นยำที่ OP เห็นถึงพฤติกรรมที่อธิบายไว้
Mark Dickinson

@ MarkDickinson ฉันค่อนข้างแน่ใจว่าโมดูโลใน c ++ ให้ค่าบวกสำหรับตัวหารที่เป็นบวก แต่ฉันจะไม่เถียง
user64742

1
@TheGreatDuck - ดูตัวอย่าง: cpp.sh/3g7h (โปรดทราบว่า C ++ 98 ไม่ได้กำหนดว่าจะใช้ตัวแปรตัวใดตัวหนึ่งในสองตัวเลือก แต่มาตรฐานที่ใหม่กว่านั้นทำดังนั้นจึงเป็นไปได้ที่คุณจะใช้งาน C ++ ในอดีตที่ทำมันแตกต่างกัน ... )
Periata Breatta
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.