Tại sao (a% 256) khác với (a & 0xFF)?


145

Tôi luôn cho rằng khi thực hiện (a % 256)trình tối ưu hóa sẽ tự nhiên sử dụng thao tác bitwise hiệu quả, như thể tôi đã viết (a & 0xFF).

Khi thử nghiệm trên trình biên dịch 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

Và khi thử mã khác:

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

mod(int):
    movzx   eax, dil
    ret

Có vẻ như tôi hoàn toàn thiếu một cái gì đó. Có ý kiến ​​gì không?


64
0xFF là 255 chứ không phải 256.
Rishikesh Raje

186
@RishikeshRaje: Vậy sao? %không phải là &một trong hai.
usr2564301

27
@RishikeshRaje: Tôi chắc chắn OP rất biết điều đó. Chúng được sử dụng với các hoạt động khác nhau.
Chúc mừng và hth. - Alf

28
Ra quan tâm, để bạn có được kết quả tốt hơn nếu numunsigned?
Bathsheba

20
@RishikeshRaje Bitwise và 0xFF tương đương với modulo 2 ^ 8 cho các số nguyên không dấu.
2501

Câu trả lời:


230

Nó không giống nhau. Hãy thử num = -79, và bạn sẽ nhận được kết quả khác nhau từ cả hai hoạt động. (-79) % 256 = -79, trong khi (-79) & 0xfflà một số tích cực.

Sử dụng unsigned int, các hoạt động là như nhau, và mã có thể sẽ giống nhau.

PS- Ai đó đã bình luận

Chúng không nên giống nhau, a % bđược định nghĩa là a - b * floor (a / b).

Đó không phải là cách nó được định nghĩa trong C, C ++, Objective-C (tức là tất cả các ngôn ngữ mà mã trong câu hỏi sẽ biên dịch).


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Martijn Pieters

53

Câu trả lời ngắn

-1 % 256sản lượng -1và không phải 255-1 & 0xFF. Do đó, việc tối ưu hóa sẽ không chính xác.

Câu trả lời dài

C ++ có quy ước rằng (a/b)*b + a%b == a, điều này có vẻ khá tự nhiên. a/bluôn trả về kết quả số học mà không có phần phân số (cắt ngắn về 0). Kết quả là, a%bcó cùng dấu ahoặc là 0.

Việc phân chia -1/256sản lượng 0và do đó -1%256phải -1đáp ứng điều kiện trên ( (-1%256)*256 + -1%256 == -1). Điều này rõ ràng là khác nhau từ -1&0xFFđó là 0xFF. Do đó, trình biên dịch không thể tối ưu hóa theo cách bạn muốn.

Phần có liên quan trong tiêu chuẩn C ++ [expr.mul §4] kể từ N4606 nêu rõ:

Đối với các toán hạng tích phân, toán /tử mang lại thương số đại số với bất kỳ phần phân số nào bị loại bỏ; nếu thương số a/bcó thể biểu thị theo loại kết quả, (a/b)*b + a%bbằng a[...].

Kích hoạt tối ưu hóa

Tuy nhiên, sử dụng unsignedcác loại, tối ưu hóa sẽ hoàn toàn chính xác , đáp ứng quy ước trên:

unsigned(-1)%256 == 0xFF

Xem thêm này .

Những ngôn ngữ khác

Điều này được xử lý rất khác nhau trên các ngôn ngữ lập trình khác nhau khi bạn có thể tra cứu trên Wikipedia .


50

Vì C ++ 11, num % 256phải không dương nếu numâm tính.

Vì vậy, mẫu bit sẽ phụ thuộc vào việc triển khai các loại đã ký trên hệ thống của bạn: đối với đối số đầu tiên phủ định, kết quả không phải là trích xuất 8 bit có ý nghĩa nhỏ nhất.

Sẽ là một vấn đề khác nếu numtrong trường hợp của bạn là unsigned: những ngày này tôi gần như mong đợi một trình biên dịch sẽ thực hiện tối ưu hóa mà bạn trích dẫn.


6
Hầu như nhưng không hoàn toàn. Nếu numlà âm, thì num % 256bằng 0 hoặc âm (hay không dương).
Nayuki

5
IMO nào, là một sai lầm trong tiêu chuẩn: phép toán modulo về mặt toán học nên lấy dấu của ước số, 256 trong trường hợp này. Để hiểu tại sao phải xem xét điều đó (-250+256)%256==6, nhưng (-250%256)+(256%256)phải, theo tiêu chuẩn, "không tích cực", và do đó thì không 6. Phá vỡ tính kết hợp như thế có tác dụng phụ ngoài đời thực: ví dụ khi tính toán kết xuất "thu nhỏ" theo tọa độ nguyên, người ta phải dịch chuyển hình ảnh để có tất cả các tọa độ không âm.
Michael

2
@Michael Modulus chưa bao giờ được phân phối bổ sung ("liên kết" là tên sai cho thuộc tính này!), Ngay cả khi bạn tuân theo định nghĩa toán học cho chữ cái. Ví dụ, (128+128)%256==0nhưng (128%256)+(128%256)==256. Có lẽ có một sự phản đối tốt đối với hành vi được chỉ định, nhưng đối với tôi, đó không phải là điều rõ ràng.
Daniel Wagner

1
@DanielWagner, bạn nói đúng, tất nhiên, tôi đánh vần sai với "kết hợp". Tuy nhiên, nếu người ta giữ dấu của ước số và tính toán mọi thứ theo số học mô-đun, thuộc tính phân phối sẽ giữ; trong ví dụ của bạn, bạn sẽ có 256==0. Điều quan trọng là phải Ncó các giá trị chính xác có thể có trong Nsố học modulo , điều này chỉ có thể nếu tất cả các kết quả nằm trong phạm vi 0,...,(N-1), không phải -(N-1),...,(N-1).
Michael

6
@Michael: Ngoại trừ% không phải là toán tử modulo, nó là toán tử còn lại .
Joren

11

Tôi không có cái nhìn sâu sắc về khả năng ngoại cảm đối với lý luận của nhà soạn nhạc, nhưng trong trường hợp %cần phải xử lý các giá trị âm (và các vòng chia về 0), trong khi &kết quả luôn là 8 bit thấp hơn.

Các sarâm thanh hướng dẫn với tôi như "chuyển số học đúng", làm đầy lên các bit bỏ trống với giá trị dấu bit.


0

Về mặt toán học, modulo được định nghĩa như sau:

a% b = a - b * sàn (a / b)

Điều này ngay tại đây sẽ làm rõ nó cho bạn. Chúng ta có thể loại bỏ sàn cho số nguyên vì phép chia số nguyên tương đương với sàn (a / b). Tuy nhiên, nếu trình biên dịch sử dụng một thủ thuật chung như bạn đề xuất, thì nó sẽ cần phải hoạt động cho tất cả a và tất cả b. Thật không may, đây không phải là trường hợp. Về mặt toán học, thủ thuật của bạn là chính xác 100% đối với các số nguyên không dấu (Tôi thấy một câu trả lời trạng thái ký số nguyên bị phá vỡ nhưng tôi có thể xác nhận cũng không phủ nhận điều này là -a% b nên dương). Tuy nhiên, bạn có thể làm thủ thuật này cho tất cả b? Chắc là không. Đó là lý do tại sao trình biên dịch không làm điều đó. Xét cho cùng, nếu modulo dễ dàng được viết dưới dạng một thao tác bitwise, thì chúng ta sẽ chỉ cần thêm một mạch modulo như để thêm và các hoạt động khác.


4
Tôi nghĩ rằng bạn đang nhầm lẫn giữa "sàn" với "cắt ngắn". Các máy tính ban đầu sử dụng phép chia cắt vì nó thường dễ tính toán hơn phân chia trôi nổi ngay cả trong trường hợp mọi thứ xảy ra để chia đều. Tôi đã thấy rất ít trường hợp phân chia cắt ngắn hữu ích hơn phân chia thả nổi, nhưng nhiều ngôn ngữ tuân theo sự dẫn dắt của FORTRAN về việc sử dụng phân chia cắt ngắn.
supercat

@supercat Về mặt toán học, sàn cắt ngắn. Cả hai đều có tác dụng như nhau. Chúng có thể không được thực hiện giống nhau trong máy tính, nhưng chúng làm điều tương tự.
dùng64742

5
@TheGreatDuck: Chúng không giống nhau cho các số âm. Tầng của -2.3-3, trong khi nếu bạn cắt ngắn -2.3thành một số nguyên bạn nhận được -2. Xem en.wikipedia.org/wiki/Truncation . "Cho phép cắt số âm không làm tròn cùng hướng với hàm sàn". Và hành vi của %các số âm chính xác là lý do mà OP đang nhìn thấy hành vi được mô tả.
Đánh dấu Dickinson

@MarkDickinson Tôi khá chắc chắn rằng modulo trong c ++ mang lại giá trị dương cho các ước số dương, nhưng tôi sẽ không tranh luận.
dùng64742

1
@TheGreatDuck - xem ví dụ: cpp.sh/3g7h (Lưu ý rằng C ++ 98 không xác định biến thể nào trong hai biến thể có thể được sử dụng, nhưng các tiêu chuẩn gần đây hơn, vì vậy có thể bạn đã sử dụng triển khai C ++ trong quá khứ đã làm điều đó khác đi ...)
Periata Breatta
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.