วิธีที่ดีที่สุดในการทำให้โมดูลัสของ Java ทำงานอย่างที่ควรจะเป็นกับตัวเลขติดลบ?


103

ใน java เมื่อคุณทำ

a % b

ถ้า a เป็นลบมันจะส่งกลับผลลัพธ์ที่เป็นลบแทนที่จะพันรอบ b อย่างที่ควรจะเป็น วิธีใดที่ดีที่สุดในการแก้ไขปัญหานี้ วิธีเดียวที่ฉันคิดได้คือ

a < 0 ? b + a : a % b

12
ไม่มีพฤติกรรมโมดูลัสที่ "ถูกต้อง" เมื่อต้องจัดการกับตัวเลขเชิงลบ - มีหลายภาษาที่ทำในลักษณะนี้ภาษาหลายภาษาก็แตกต่างกันไปและบางภาษาก็ทำบางอย่างที่แตกต่างไปจากเดิมอย่างสิ้นเชิง อย่างน้อยสองข้อแรกมีข้อดีข้อเสีย

4
นี่เป็นเรื่องแปลกสำหรับฉัน ฉันคิดว่ามันควรจะส่งกลับค่าลบก็ต่อเมื่อ b เป็นลบ
ปัดป้อง


2
มันคือ. แต่ควรเปลี่ยนชื่อคำถามนั้น ฉันจะไม่คลิกคำถามนั้นหากฉันกำลังค้นหาคำถามนี้เพราะฉันรู้แล้วว่าโมดูลัส java ทำงานอย่างไร
ปัดป้อง

4
ฉันเพิ่งเปลี่ยนชื่อเป็น "ทำไม -13% 64 = 51" ซึ่งจะไม่มีวันมีใครค้นหาในอีกล้านปี ดังนั้นชื่อคำถามนี้ดีกว่ามากและสามารถค้นหาคำหลักได้มากขึ้นเช่นโมดูลัสเชิงลบการคำนวณตัวเลข
Erick Robertson

คำตอบ:


144

มันทำงานตามที่ควร a% b = a - a / b * b; นั่นคือส่วนที่เหลือ

คุณสามารถทำได้ (a% b + b)% b


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


3
ทำงานได้ดีขึ้นขอบคุณ และใช้ได้กับจำนวนลบที่มากกว่า b มากด้วย
ปัดป้อง

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

1
@eitanfar ฉันได้รวมคำอธิบายที่ยอดเยี่ยมของคุณไว้ในคำตอบแล้ว (พร้อมการแก้ไขเล็กน้อยสำหรับa < 0บางทีคุณอาจได้ดู)
Maarten Bodewes

5
ฉันเพิ่งเห็นสิ่งนี้แสดงความคิดเห็นในคำถามอื่นเกี่ยวกับหัวข้อเดียวกัน มันอาจจะมีมูลค่าการกล่าวขวัญว่า(a % b + b) % bแบ่งลงสำหรับค่ามากและa bตัวอย่างเช่นการใช้a = Integer.MAX_VALUE - 1และb = Integer.MAX_VALUEจะให้-3ผลลัพธ์ซึ่งเป็นจำนวนลบซึ่งเป็นสิ่งที่คุณต้องการหลีกเลี่ยง
Thorbear

2
@Mikepote โดยใช้ a whileจะช้ากว่าถ้าคุณต้องการจริงๆยกเว้นคุณต้องการแค่ifในกรณีที่มันเร็วขึ้นจริง
Peter Lawrey

92

ในฐานะของ Java 8 คุณสามารถใช้Math.floorMod (int x, y int)และMath.floorMod (ยาว x, y ยาว) ทั้งสองวิธีนี้ให้ผลลัพธ์เหมือนกันกับคำตอบของเปโตร

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

1
คำตอบที่ดีที่สุดสำหรับ Java 8+
Charney Kaye

เจ๋งไม่ทราบเกี่ยวกับเรื่องนั้น Java 8 แก้ไข PITA บางส่วนอย่างชัดเจน
Franz D.

4
ทางที่ดี. แต่น่าเสียดายที่ใช้ไม่ได้กับfloatหรือdoubleข้อโต้แย้ง Mod binary operator ( %) ยังทำงานร่วมกับfloatและdoubleตัวถูกดำเนินการ
Mir-Ismaili

11

สำหรับผู้ที่ไม่ได้ใช้ (หรือไม่สามารถใช้งานได้) Java 8 แต่ Guava มาช่วยด้วยIntMath.mod ()ซึ่งมีให้ตั้งแต่ Guava 11.0

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

ข้อแม้อย่างหนึ่ง: ไม่เหมือนกับ Math.floorMod () ของ Java 8 ตัวหาร (พารามิเตอร์ที่สอง) ไม่สามารถเป็นค่าลบได้


7

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

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

เพราะ 180 * (-1) + 176 = -4 เหมือนกับ 180 * 0 + 176 = 176

เมื่อใช้ตัวอย่างนาฬิกาที่นี่http://mathworld.wolfram.com/Congruence.html คุณจะไม่บอกว่าช่วงเวลาการทำงานของช่วงเวลา _of_time mod cycle_length คือ -45 นาทีคุณจะบอกว่า 15 นาทีแม้ว่าคำตอบทั้งสองจะเป็นไปตามสมการพื้นฐานก็ตาม


1
ในทฤษฎีจำนวนมันไม่ได้เป็นบวกเสมอไป ... พวกมันอยู่ในชั้นเรียนที่สอดคล้องกัน คุณมีอิสระที่จะเลือกผู้สมัครคนใดก็ได้จากชั้นเรียนนั้นเพื่อจุดประสงค์ด้านสัญกรณ์ของคุณ แต่แนวคิดก็คือมันจับคู่กับทุกชั้นเรียนนั้นและหากใช้ผู้สมัครคนอื่นที่เฉพาะเจาะจงจากนั้นจะทำให้ปัญหาบางอย่างง่ายขึ้นอย่างมาก (การเลือก-1แทนที่จะn-1เป็นเช่น) แล้วมีที่มัน
BeUndead

2

Java 8 มี Math.floorModแต่มันช้ามาก (การใช้งานมีหลายหน่วยงานการคูณและเงื่อนไข) เป็นไปได้ว่า JVM มีต้นขั้วที่ได้รับการปรับให้เหมาะสมภายในซึ่งจะทำให้เร็วขึ้นอย่างมาก

วิธีที่เร็วที่สุดในการทำเช่นนี้floorModก็เหมือนกับคำตอบอื่น ๆ ที่นี่ แต่ไม่มีสาขาตามเงื่อนไขและมีเพียงคำตอบเดียวเท่านั้น% op

สมมติว่า n เป็นบวกและ x อาจเป็นอะไรก็ได้:

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;

ผลลัพธ์เมื่อn = 3:

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

หากคุณต้องการเพียงการแจกแจงแบบสม่ำเสมอระหว่าง0และn-1ไม่ใช่ตัวดำเนินการ mod ที่แน่นอนและคุณxไม่ได้รวมกลุ่มไว้ใกล้0ๆ สิ่งต่อไปนี้จะเร็วยิ่งขึ้นเนื่องจากมีความขนานในระดับคำสั่งมากขึ้นและการ%คำนวณช้าจะเกิดขึ้นควบคู่ไปกับอีก ชิ้นส่วนเนื่องจากไม่ได้ขึ้นอยู่กับผลลัพธ์ของมัน

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

ผลลัพธ์ข้างต้นกับ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

หากอินพุตเป็นแบบสุ่มในช่วงเต็มของ int การแจกแจงของทั้งสองโซลูชันจะเหมือนกัน หากคลัสเตอร์อินพุตใกล้ศูนย์จะมีผลลัพธ์น้อยเกินไปn - 1ในโซลูชันหลัง


1

นี่คือทางเลือก:

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

ซึ่งอาจเร็วกว่าสูตรอื่น [(a% b + b)% b] หรือไม่ก็ได้ แตกต่างจากสูตรอื่นคือประกอบด้วยสาขา แต่ใช้การดำเนินการโมดูโลน้อยกว่าหนึ่งรายการ อาจเป็นไปได้ว่าคอมพิวเตอร์สามารถทำนาย <0 ได้อย่างถูกต้อง

(แก้ไข: แก้ไขสูตร)


1
แต่การทำงานของโมดูโลนั้นจำเป็นต้องมีการแบ่งส่วนซึ่งอาจช้ากว่าด้วยซ้ำ (โดยเฉพาะอย่างยิ่งถ้าโปรเซสเซอร์เดาสาขาได้ถูกต้องเกือบตลอดเวลา) นี่อาจจะดีกว่า
dave

@KarstenR. คุณพูดถูก! ฉันแก้ไขสูตรแล้วตอนนี้ใช้งานได้ดี (แต่ต้องการการลบอีกสองครั้ง)
Stefan Reich

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