Hoạt động modulo với số âm


190

Trong một chương trình C, tôi đã thử các thao tác dưới đây (Chỉ để kiểm tra hành vi)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

đã cho tôi đầu ra như (2, -2 , -2)trong gcc. Tôi đã mong đợi một kết quả tích cực mỗi lần. Một mô-đun có thể là âm? Bất cứ ai có thể giải thích hành vi này?


Bản sao có thể có của stackoverflow.com/questions/4003232/ Kẻ
james

Câu trả lời:


167

C99 yêu cầu khi a/bcó thể biểu diễn:

(a/b) * b + a%b sẽ bằnga

Điều này có ý nghĩa, hợp lý. Đúng?

Hãy xem điều này dẫn đến điều gì:


Ví dụ A. 5/(-3)-1

=> (-1) * (-3) + 5%(-3) =5

Điều này chỉ có thể xảy ra nếu 5%(-3)là 2.


Ví dụ B. (-5)/3-1

=> (-1) * 3 + (-5)%3 =-5

Điều này chỉ có thể xảy ra nếu (-5)%3-2


1
Trình biên dịch có đủ thông minh và phát hiện ra rằng một modulo không dấu khác không dấu luôn luôn dương? Hiện tại (tốt, GCC 5.2) trình biên dịch dường như nghĩ rằng "%" trả về "int" trong trường hợp này, thay vì "không dấu" ngay cả khi cả hai toán hạng là uint32_t hoặc lớn hơn.
Frederick Nord

@FrederickNord Bạn có một ví dụ để thể hiện hành vi đó không?
chux - Tái lập Monica

10
Hiểu rằng những gì bạn mô tả là mô tả int (a / b) (cắt ngắn) thông thường của mod. Nhưng cũng có thể quy tắc là sàn (a / b) (Knuth). Trong trường hợp Knuth -5/3-2và mod trở thành 1. Tóm lại: một mô-đun có một dấu hiệu theo dấu cổ tức (cắt ngắn), mô-đun kia có một dấu hiệu theo dấu hiệu chia (Knuth).
Isaac

1
Đây là một trường hợp của tiêu chuẩn C chính xác không phải là điều tôi muốn. Tôi chưa bao giờ muốn rút ngắn số 0 hoặc số modulo âm, nhưng thường muốn ngược lại và cần phải làm việc xung quanh C.
Joe

141

Các %nhà điều hành trong C không phải là modulo nhà điều hành nhưng còn điều hành.

Toán tử Modulo và phần còn lại khác nhau đối với các giá trị âm.

Với một toán tử còn lại, dấu hiệu của kết quả giống như dấu hiệu của cổ tức trong khi với toán tử modulo, dấu hiệu của kết quả giống như ước số.

C định nghĩa %hoạt động cho a % b:

  a == (a / b * b) + a % b

với /sự phân chia số nguyên với cắt ngắn về phía 0. Đó là việc cắt ngắn được thực hiện đối với 0(và không hướng tới sự không có giá trị âm) xác định %toán tử còn lại thay vì toán tử modulo.


7
Phần còn lại là kết quả của hoạt động modulo theo định nghĩa. Không nên có cái gọi là toán tử còn lại bởi vì không có thứ gọi là toán tử còn lại, nó được gọi là modulo.
gronostaj

41
@gronostaj không có trong CS. Nhìn vào các ngôn ngữ cấp cao hơn như Haskell hoặc Scheme, cả hai đều xác định hai toán tử khác nhau ( remaindermodulotrong Lược đồ remmodtrong Haskell). Các thông số kỹ thuật của các toán tử này khác nhau về các ngôn ngữ này về cách phân chia được thực hiện: cắt ngắn về 0 hoặc hướng tới vô cực âm. Bằng cách tiêu chuẩn C không bao giờ gọi là %các toán tử modulo , họ chỉ đặt tên cho nó trở thành nhà điều hành% .
ouah

2
Đừng nhầm lẫn với remainder chức năng trong C, trong đó thực hiện phần còn lại của IEEE với ngữ nghĩa tròn hướng tới gần nhất trong bộ phận
Eric

67

Dựa trên Thông số kỹ thuật của C99: a == (a / b) * b + a % b

Chúng ta có thể viết một hàm để tính toán (a % b) == a - (a / b) * b!

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

Đối với hoạt động modulo, chúng ta có thể có chức năng sau (giả sử b > 0)

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

Kết luận của tôi là a % btrong C là một phép toán còn lại và KHÔNG phải là phép toán modulo.


3
Điều này không cho kết quả tích cực khi bâm tính (và trên thực tế rbcả âm tính nó cho kết quả nhỏ hơn -b). Để đảm bảo kết quả tích cực cho tất cả các đầu vào bạn có thể sử dụng r + abs(b)hoặc để khớp với bdấu hiệu của bạn, bạn có thể thay đổi điều kiện thành r*b < 0thay thế.
Martin Ender

@MartinEnder r + abs(b)là UB khi b == INT_MIN.
chux - Phục hồi Monica

60

Tôi không nghĩ rằng không cần phải kiểm tra nếu số âm.

Một hàm đơn giản để tìm modulo dương sẽ là cái này -

Chỉnh sửa: Giả sử N > 0N + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

Điều này sẽ làm việc cho cả giá trị dương và âm của x.

PS gốc: cũng như được chỉ ra bởi @chux, Nếu x và N của bạn có thể đạt được thứ gì đó tương ứng như INT_MAX-1 và INT_MAX, chỉ cần thay thế intbằng long long int.

Và nếu chúng cũng vượt quá giới hạn dài (ví dụ gần LLONG_MAX), thì bạn sẽ xử lý riêng các trường hợp tích cực và tiêu cực như được mô tả trong các câu trả lời khác ở đây.


1
Lưu ý rằng khi N < 0, kết quả có thể âm tính như trong modulo(7, -3) --> -2. Cũng x % N + Ncó thể tràn inttoán học là hành vi không xác định. ví dụ modulo(INT_MAX - 1,INT_MAX)có thể dẫn đến -3.
chux - Phục hồi Monica

Có, trong trường hợp đó, bạn có thể chỉ cần sử dụng long long inthoặc xử lý riêng trường hợp tiêu cực (với chi phí mất tính đơn giản).
Udayraj Deshmukh

9

Các câu trả lời khác đã giải thích trong C99 trở lên, phân chia số nguyên liên quan đến toán hạng âm luôn bị cắt về không .

Lưu ý rằng, trong C89 , cho dù kết quả làm tròn lên hay xuống đều được xác định theo thực hiện. Vì (a/b) * b + a%bbằng avới tất cả các tiêu chuẩn, kết quả của việc %liên quan đến các toán hạng âm cũng được xác định theo triển khai trong C89.


5

Một mô-đun có thể là âm?

%có thể âm vì nó là toán tử còn lại , phần còn lại sau khi chia, không phải sau Euclidean_division . Vì C99 kết quả có thể là 0, âm hoặc dương.

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

Các modulo OP muốn là một cổ điển modulo Euclide , không %.

Tôi đã mong đợi một kết quả tích cực mỗi lần.

Để thực hiện một modulo Euclide được xác định rõ bất cứ khi nào a/bđược xác định, a,bcó bất kỳ dấu hiệu nào và kết quả không bao giờ âm:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

Kết quả của hoạt động Modulo phụ thuộc vào dấu của tử số, và do đó bạn nhận được -2 cho yz

Đây là tài liệu tham khảo

http://www.oolie.fu-berlin.de/oolnet/use/info/libc/libc_14.html

Bộ phận nguyên

Phần này mô tả các chức năng để thực hiện phân chia số nguyên. Các hàm này là dự phòng trong thư viện GNU C, vì trong GNU C, toán tử '/' luôn làm tròn về 0. Nhưng trong các triển khai C khác, '/' có thể làm tròn khác với các đối số phủ định. div và ldiv rất hữu ích vì chúng chỉ định cách làm tròn thương số: về không. Phần còn lại có cùng dấu với tử số.


5
Bạn đang đề cập đến một văn bản về ANSI C. Đây là một quy tắc khá cũ của C. Không chắc văn bản đó có đúng với ANSI C hay không, nhưng chắc chắn không liên quan đến C99. Trong phân chia số nguyên C99 §6.5.5 được xác định để luôn luôn cắt về không.
Palec

2

Trong Toán học, nơi những quy ước này xuất phát, không có sự khẳng định rằng số học modulo sẽ mang lại kết quả tích cực.

Ví dụ.

1 mod 5 = 1, nhưng nó cũng có thể bằng -4. Nghĩa là, 1/5 mang lại phần còn lại 1 từ 0 hoặc -4 từ 5. (Cả hai yếu tố của 5)

Tương tự, -1 mod 5 = -1, nhưng nó cũng có thể bằng 4. Nghĩa là, -1/5 mang lại -1 còn lại từ 0 hoặc 4 từ -5. (Cả hai yếu tố của 5)

Để đọc thêm hãy nhìn vào các lớp tương đương trong Toán học.


Lớp tương đương là một khái niệm khác và modulo được định nghĩa theo một cách rất nghiêm ngặt. Hãy nói rằng chúng ta có hai số nguyên ab, b <> 0. Theo định lý phân chia Euclide tồn tại chính xác một cặp số nguyên m, rở đâu a = m * b + r0 <= r < abs( b ). Điều được nói rlà kết quả của hoạt động modulo (toán học) và theo định nghĩa là không âm. Đọc thêm và liên kết nhiều hơn trên Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Ister

Điều đó không đúng. 1 mod 5luôn luôn là 1. -4 mod 5có thể là 1 quá, nhưng chúng là những thứ khác nhau.
FelipeC

2

Theo tiêu chuẩn C99 , phần 6.5.5 Toán tử nhân , yêu cầu sau:

(a / b) * b + a % b = a

Phần kết luận

Dấu hiệu của kết quả của một hoạt động còn lại, theo C99, giống như kết quả của cổ tức.

Hãy xem một số ví dụ ( dividend / divisor):

Khi chỉ có cổ tức là âm

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Khi chỉ có ước số là âm

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

Khi cả hai số chia và cổ tức đều âm

(-3 / -2) * -2  +  -3 % -2 = -3

(-3 / -2) * -2 = -2

(-3 % -2) must be -1

6.5.5 Toán tử nhân

Cú pháp

  1. biểu thức nhân:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

Những ràng buộc

  1. Mỗi toán hạng sẽ có kiểu số học. Các toán hạng của toán tử % sẽ có kiểu số nguyên.

Ngữ nghĩa

  1. Các chuyển đổi số học thông thường được thực hiện trên các toán hạng.

  2. Kết quả của toán tử nhị phân * là sản phẩm của toán hạng.

  3. Kết quả của toán tử / là thương số từ việc chia toán hạng thứ nhất cho toán tử thứ hai; kết quả của toán tử % là phần còn lại. Trong cả hai thao tác, nếu giá trị của toán hạng thứ hai bằng 0, thì hành vi không được xác định.

  4. Khi các số nguyên được chia, kết quả của toán tử / là thương số đại số với bất kỳ phần phân số nào bị loại bỏ [1]. Nếu thương số a/blà đại diện, biểu thức (a/b)*b + a%bsẽ bằng a.

[1]: Điều này thường được gọi là "cắt ngắn về không".


1

Toán tử mô đun cho phần còn lại. Toán tử mô đun trong c thường lấy dấu của tử số

  1. x = 5% (-3) - ở đây tử số là dương do đó kết quả là 2
  2. y = (-5)% (3) - ở đây tử số âm do đó có kết quả -2
  3. z = (-5)% (-3) - ở đây tử số âm do đó có kết quả -2

Ngoài ra toán tử modulus (phần còn lại) chỉ có thể được sử dụng với loại số nguyên và không thể được sử dụng với dấu phẩy động.


1
Sẽ thật tuyệt nếu bạn có thể sao lưu điều này bằng các liên kết đến các tài nguyên bên ngoài.
J ... S

1

Tôi tin rằng sẽ hữu ích hơn khi nghĩ về modnó như được định nghĩa trong số học trừu tượng; không phải là một hoạt động, mà là một lớp số học hoàn toàn khác nhau, với các yếu tố khác nhau và các toán tử khác nhau. Điều đó có nghĩa là bổ sung mod 3không giống như bổ sung "bình thường"; đó là; cộng số nguyên.

Vì vậy, khi bạn làm:

5 % -3

Bạn đang cố gắng ánh xạ số nguyên 5 đến một phần tử trong tập hợp mod -3. Đây là những yếu tố của mod -3:

{ 0, -2, -1 }

Vì thế:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

Nói rằng bạn phải ở lại vì một số lý do 30 giờ, bạn sẽ còn lại bao nhiêu giờ trong ngày đó? 30 mod -24.

Nhưng những gì C thực hiện không phải mod, đó là phần còn lại. Dù sao, vấn đề là nó có ý nghĩa để trả lại tiêu cực.

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.