Cách tốt nhất để làm cho mô-đun của Java hoạt động giống như nó nên làm với số âm?


103

Trong java khi bạn làm

a % b

Nếu a là âm, nó sẽ trả về kết quả âm, thay vì quấn quanh b như bình thường. Cách tốt nhất để sửa lỗi này là gì? Cách duy nhất tôi có thể nghĩ là

a < 0 ? b + a : a % b

12
Không có hành vi mô đun "đúng" khi xử lý số âm - nhiều ngôn ngữ làm theo cách này, nhiều ngôn ngữ làm theo cách khác và một vài ngôn ngữ làm điều gì đó hoàn toàn khác. Ít nhất hai phần đầu có ưu và nhược điểm của chúng.

4
điều này thật kỳ lạ đối với tôi. tôi nghĩ rằng nó chỉ nên trả về âm nếu b là tiêu cực.
Fent


2
nó là. nhưng tiêu đề của câu hỏi đó nên được đổi tên. Tôi sẽ không nhấp vào câu hỏi đó nếu tôi đang tìm kiếm câu hỏi này vì tôi đã biết cách hoạt động của java modulus.
Fent

4
Tôi vừa đổi tên nó thành "Tại sao -13% 64 = 51?", Điều mà người ta sẽ không bao giờ tìm kiếm trong một triệu năm nữa. Vì vậy, tiêu đề câu hỏi này tốt hơn nhiều và có thể tìm kiếm nhiều hơn trên các từ khóa như mô đun, phủ định, phép tính, số.
Erick Robertson

Câu trả lời:


144

Nó hoạt động như nó phải a% b = a - a / b * b; tức là nó là phần còn lại.

Bạn có thể làm (a% b + b)% b


Biểu thức này hoạt động vì kết quả của (a % b)nhất thiết phải thấp hơn b, bất kể alà dương hay âm. Việc thêm bsẽ quan tâm đến các giá trị âm a, vì (a % b)là giá trị âm giữa -b0, (a % b + b)nhất thiết phải thấp hơn bvà dương. Mô-đun cuối cùng ở đó trong trường hợp abắt đầu là tích cực, vì nếu alà tích cực (a % b + b)sẽ trở nên lớn hơn b. Do đó, hãy (a % b + b) % bbiến nó thành nhỏ hơn bmột lần nữa (và không ảnh hưởng đến các agiá trị âm ).


3
điều này hoạt động tốt hơn cảm ơn. và nó cũng hoạt động cho các số âm lớn hơn b nhiều.
Fent

6
Nó hoạt động vì kết quả của (a % b)nhất thiết phải thấp hơn b(bất kể alà tích cực hay tiêu cực), việc thêm bsẽ quan tâm đến các giá trị âm của a, vì (a % b)thấp hơn bvà thấp hơn 0, (a % b + b)nhất thiết phải thấp hơn bvà dương. Mô-đun cuối cùng ở đó trong trường hợp abắt đầu là tích cực, vì nếu alà tích cực (a % b + b)sẽ trở nên lớn hơn b. Do đó, hãy (a % b + b) % bbiến nó thành nhỏ hơn bmột lần nữa (và không ảnh hưởng đến các agiá trị âm ).
ethanfar

1
@eitanfar Tôi đã bao gồm lời giải thích tuyệt vời của bạn vào câu trả lời (với một sự điều chỉnh nhỏ cho a < 0, có lẽ bạn có thể có một cái nhìn)
Maarten Bodewes

5
Tôi chỉ thấy điều này nhận xét về một câu hỏi khác liên quan đến cùng một chủ đề; Có thể đáng nói là (a % b + b) % bchia nhỏ cho các giá trị rất lớn của ab. Ví dụ, sử dụng a = Integer.MAX_VALUE - 1b = Integer.MAX_VALUEsẽ cho -3kết quả là một số âm, đó là điều bạn muốn tránh.
Thorbear

2
@Mikepote sử dụng a whilesẽ chậm hơn nếu bạn thực sự cần ngoại trừ bạn chỉ cần một iftrong trường hợp đó, nó thực sự nhanh hơn.
Peter Lawrey

92

Kể từ Java 8, bạn có thể sử dụng Math.floorMod (int x, int y)Math.floorMod (long x, long y) . Cả hai phương pháp này đều trả về kết quả giống như câu trả lời của Peter.

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2

1
câu trả lời hay nhất cho Java 8+
Charney Kaye

Tuyệt, không biết về cái đó. Java 8 đã sửa một số lỗi PITA.
Franz D.

4
Cách tốt. Nhưng tiếc là không hoạt động với floathoặc doubleđối số. Toán tử nhị phân mod ( %) cũng hoạt động với floatdoubletoán hạng.
Mir-Ismaili

11

Đối với những người chưa sử dụng (hoặc không thể sử dụng) Java 8, Guava đã giải cứu với IntMath.mod () , có sẵn kể từ Guava 11.0.

IntMath.mod( 2, 3) = 2
IntMath.mod(-2, 3) = 1

Một lưu ý: không giống như Math.floorMod () của Java 8, số chia (tham số thứ hai) không được âm.


7

Trong lý thuyết số, kết quả luôn là số dương. Tôi đoán rằng điều này không phải lúc nào cũng đúng trong ngôn ngữ máy tính vì không phải tất cả các lập trình viên đều là nhà toán học. Hai xu của tôi, tôi sẽ coi đó là một khiếm khuyết thiết kế của ngôn ngữ, nhưng bạn không thể thay đổi nó bây giờ.

= MOD (-4,180) = 176 = MOD (176, 180) = 176

vì 180 * (-1) + 176 = -4 giống 180 * 0 + 176 = 176

Sử dụng ví dụ về đồng hồ ở đây, http://mathworld.wolfram.com/Congruence.html , bạn sẽ không nói thời lượng_of_time mod cycle_length là -45 phút, bạn sẽ nói là 15 phút, mặc dù cả hai câu trả lời đều thỏa mãn phương trình cơ sở.


1
Trong lý thuyết số, nó không phải lúc nào cũng tích cực ... Họ rơi vào các lớp đồng dư. Bạn có thể tự do chọn bất kỳ ứng cử viên nào từ lớp đó cho mục đích ký hiệu của mình, nhưng ý tưởng là nó ánh xạ đến tất cả lớp đó và nếu sử dụng một ứng viên cụ thể khác từ lớp đó sẽ làm cho một vấn đề nhất định đơn giản hơn đáng kể ( ví dụ: chọn -1thay vì n-1) sau đó có tại nó.
BeUndead

2

Java 8 có Math.floorMod, nhưng nó rất chậm (triển khai của nó có nhiều phép chia, phép nhân và một điều kiện). Tuy nhiên, có thể JVM có một sơ khai được tối ưu hóa nội tại cho nó, điều này sẽ tăng tốc đáng kể.

Cách nhanh nhất để làm điều này mà không floorModcó giống như một số câu trả lời khác ở đây, nhưng không có nhánh có điều kiện và chỉ có một %op chậm .

Giả sử n là số dương và x có thể là bất kỳ thứ gì:

int remainder = (x % n); // may be negative if x is negative
//if remainder is negative, adds n, otherwise adds 0
return ((remainder >> 31) & n) + remainder;

Kết quả khi n = 3:

x | result
----------
-4| 2
-3| 0
-2| 1
-1| 2
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1

Nếu bạn chỉ cần sự phân bố đồng đều giữa 0n-1và không phải toán tử mod chính xác, và của bạn xkhông cụm lại gần 0, thì phần sau sẽ thậm chí còn nhanh hơn, vì có nhiều mức lệnh song song hơn và %tính toán chậm sẽ xảy ra song song với phần khác vì chúng không phụ thuộc vào kết quả của nó.

return ((x >> 31) & (n - 1)) + (x % n)

Kết quả cho phần trên với n = 3:

x | result
----------
-5| 0
-4| 1
-3| 2
-2| 0
-1| 1
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1
 5| 2

Nếu đầu vào là ngẫu nhiên trong phạm vi đầy đủ của một int, phân phối của cả hai nghiệm sẽ giống nhau. Nếu các cụm đầu vào gần bằng 0, sẽ có quá ít kết quả ở n - 1giải pháp thứ hai.


1

Đây là một giải pháp thay thế:

a < 0 ? b-1 - (-a-1) % b : a % b

Công thức này có thể nhanh hơn hoặc không nhanh hơn công thức khác [(a% b + b)% b]. Không giống như công thức khác, nó chứa một nhánh, nhưng sử dụng một thao tác ít mô đun hơn. Có thể là một chiến thắng nếu máy tính có thể dự đoán đúng <0.

(Chỉnh sửa: Đã sửa công thức.)


1
Nhưng hoạt động mô-đun yêu cầu một sự phân chia thậm chí có thể chậm hơn (đặc biệt nếu bộ xử lý đoán đúng nhánh hầu như mọi lúc). Vì vậy, điều này có thể tốt hơn.
dave

@KarstenR. Bạn đúng rồi! Tôi đã sửa công thức, bây giờ nó hoạt động tốt (nhưng cần thêm hai phép trừ).
Stefan Reich,

Đó là sự thật @dave
Stefan Reich
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.