เหตุผลใดที่ใช้เมื่อผู้ออกแบบโปรแกรมภาษาตัดสินใจว่าสัญญาณของการดำเนินการแบบโมดูโลนั้นทำอะไร


9

ต้องผ่านการทำงานของโมดูโล่ (ถนนที่ฉันเข้ามาขณะสำรวจความแตกต่างระหว่างremและmod ) ฉันเจอ:

ในวิชาคณิตศาสตร์ผลของการดำเนินการโมดูโลคือส่วนที่เหลือของการหารแบบยุคลิด อย่างไรก็ตามการประชุมอื่น ๆ เป็นไปได้ คอมพิวเตอร์และเครื่องคิดเลขมีวิธีจัดเก็บและแสดงตัวเลขต่าง ๆ ดังนั้นคำจำกัดความของการดำเนินการโมดูโลจึงขึ้นอยู่กับภาษาการเขียนโปรแกรมและ / หรือฮาร์ดแวร์พื้นฐาน

คำถาม:

  • การผ่านส่วนยูคลิดฉันพบว่าส่วนที่เหลือของการดำเนินการนี้เป็นบวกเสมอ (หรือ 0) ข้อ จำกัด ของฮาร์ดแวร์คอมพิวเตอร์พื้นฐานบังคับให้นักออกแบบการเขียนโปรแกรมภาษาแตกต่างจากคณิตศาสตร์อย่างไร
  • ภาษาการเขียนโปรแกรมทุกภาษามีกฎที่กำหนดไว้ล่วงหน้าหรือไม่ได้กำหนดซึ่งเป็นผลมาจากการดำเนินการ modulo ที่ได้รับมันเป็นสัญญาณ เหตุผลอะไรที่ถูกนำไปใช้ในขณะที่ทำกฎเหล่านี้ และหากฮาร์ดแวร์พื้นฐานเป็นสิ่งที่น่ากังวลกฎก็ไม่ควรเปลี่ยนแปลงตามนั้นความเป็นอิสระของภาษาโปรแกรม?

1
ในรหัสของฉันฉันมักจะต้องการโมดูโลไม่ใช่ส่วนที่เหลือ ไม่มีความคิดว่าทำไมส่วนที่เหลือจึงเป็นที่นิยม
CodesInChaos

8
ที่เกี่ยวข้องความแตกต่างคืออะไร? Remainder vs Modulus - บล็อกของ Eric Lippert (โดยหนึ่งในนักออกแบบ C # แต่ฉันเชื่อว่าเขาเข้าร่วมทีมหลังจากการตัดสินใจครั้งนี้)
CodesInChaos

1
หากคุณอ่านบทความ Wikipedia ต่อไป (นอกเหนือจากส่วนที่คุณยกมา) มันจะอธิบายสิ่งที่คุณยกมาค่อนข้างดี คำอธิบายนั้นเกี่ยวกับอะไรที่คุณสับสน?
Robert Harvey

1
คำถามที่เกี่ยวข้องอย่างหนึ่งคือการดำเนินการใดที่ตรงกับคำสั่งของ CPU ใน c คือการติดตั้งที่กำหนดไว้ซึ่งสอดคล้องกับปรัชญาของการทำแผนที่โดยตรงกับฮาร์ดแวร์บนแพลตฟอร์มให้ได้มากที่สุด ดังนั้นจึงไม่ได้ระบุสิ่งที่อาจแตกต่างกันระหว่าง CPU
CodesInChaos

5
@BleedingFingers Programming (-3)/2 == -1มักจะใช้แบ่งจำนวนเต็มที่จะไปสู่การเป็นศูนย์เช่น คำจำกัดความนี้มีประโยชน์ เมื่อคุณต้องการ%ที่จะสอดคล้องกับส่วนนี้ตอบสนองx == (x/y)*y + x % yคุณท้ายด้วยความหมายของการ%ใช้ใน C #
CodesInChaos

คำตอบ:


7

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

ความคาดหวังทั่วไปของภาษาคอมพิวเตอร์ส่วนใหญ่คือ (a div b) * b + (a mod b) = a กล่าวอีกนัยหนึ่ง div และ mod ที่พิจารณาด้วยกันจะแบ่งตัวเลขออกเป็นส่วนต่างๆที่สามารถนำกลับมารวมกันได้อย่างน่าเชื่อถือ ข้อกำหนดนี้มีความชัดเจนในมาตรฐาน C ++ แนวคิดนี้เกี่ยวข้องกับการจัดทำดัชนีของอาร์เรย์หลายมิติ ฉันใช้มันบ่อยๆ

จากนี้จะเห็นได้ว่า div และ mod จะรักษาสัญลักษณ์ของ a ถ้า b เป็นค่าบวก (ตามปกติ)

บางภาษามีฟังก์ชั่น 'rem ()' ที่เกี่ยวข้องกับ mod และมีเหตุผลทางคณิตศาสตร์อื่น ๆ ฉันไม่เคยต้องการใช้สิ่งนี้ ดูตัวอย่าง frem () ใน Gnu C. [แก้ไข]


ฉันคิดว่าrem(a,b)มันเป็น likley มากกว่าที่จะmod(a,b)เป็นในเชิงบวกหรือmod(a,b) + bไม่
user40989

3
(a div b) * b + (a mod b) = a- อย่างนี้มาก ในความเป็นจริงตรงกันข้ามกับวิธีการที่วิกิพีเดียอธิบายขยายไปยังตัวเลขที่ติดลบในส่วนของยูคลิด (โดยเฉพาะอย่างยิ่ง "ส่วนที่เหลือเป็นเพียงคนเดียวของตัวเลขสี่ที่ไม่สามารถเป็นค่าลบ.") สร้างความสับสนให้ฉันเพราะฉันถูกเสมอสอนว่าส่วนที่เหลือสามารถเป็นลบ ในทุกวิชาคณิตศาสตร์ในระดับนั้น
Izkata

@ user40989: ฉันบอกว่าฉันไม่เคยใช้ ดูการแก้ไข!
david.pfx

4

สำหรับการเขียนโปรแกรมโดยทั่วไปคุณต้องการX == (X/n)*n + X%n; ดังนั้นวิธีการกำหนดโมดูโลขึ้นอยู่กับวิธีการแบ่งจำนวนเต็ม

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

จริงๆมีประมาณ 7 ตัวเลือก:

  • ปัดเศษเป็นลบอนันต์
  • รอบเป็นอินฟินิตี้ในเชิงบวก
  • รอบเป็นศูนย์
  • หลายรอบของ "การปัดเศษที่ใกล้ที่สุด" (มีความแตกต่างในลักษณะคล้าย 0.5 ถูกปัดเศษ)

-( (-X) / n) == X/nตอนนี้พิจารณา ฉันต้องการให้สิ่งนี้เป็นจริงเนื่องจากสิ่งอื่น ๆ ดูเหมือนไม่สอดคล้องกัน (เป็นจริงสำหรับจุดลอยตัว) และไร้เหตุผล (อาจเป็นสาเหตุของข้อผิดพลาด สิ่งนี้ทำให้ 2 ตัวเลือกแรกสำหรับการหารจำนวนเต็ม (การปัดเศษไปทางอนันต์) ไม่เป็นที่ต้องการ

ตัวเลือก "รอบไปสู่ที่ใกล้ที่สุด" ทั้งหมดเป็นความเจ็บปวดที่คอสำหรับการเขียนโปรแกรมโดยเฉพาะเมื่อคุณทำบางอย่างเช่นบิตแมป (เช่นoffset = index / 8; bitNumber = index%8;)

ซึ่งทำให้การปัดเศษเป็นศูนย์เป็นตัวเลือก "อาจมีเหตุผลมากที่สุด" ซึ่งหมายความว่า modulo ส่งคืนค่าที่มีเครื่องหมายเดียวกับตัวเศษ (หรือศูนย์)

หมายเหตุ: คุณจะทราบด้วยว่า CPU ส่วนใหญ่ (CPU ทั้งหมดที่ฉันรู้จัก) ทำการหารจำนวนเต็มด้วยวิธี "round to zero" นี่อาจเป็นเพราะเหตุผลเดียวกัน


แต่การตัดทอนก็มีความไม่สอดคล้องกันเช่นกัน: มันแตก(a+b*c)/b == a % bและa >> n == a / 2 ** nซึ่งการแบ่งพื้นมีพฤติกรรมที่มีสติ
dan04

ตัวอย่างแรกของคุณไม่สมเหตุสมผล ตัวอย่างที่สองของคุณเป็นระเบียบสำหรับโปรแกรมเมอร์: สำหรับ a บวกและบวก n มันสอดคล้องกันสำหรับลบ a และบวก n มันขึ้นอยู่กับการกำหนดกะขวา (เลขคณิตเทียบกับตรรกะ) และสำหรับลบ n มันหัก (เช่น1 >> -2 == a / 2 ** (-2))
เบรนแดน

ตัวอย่างแรกคือตัวพิมพ์ผิด: ฉันหมายถึง(a + b * c) % b == a % bคือตัว%ดำเนินการคือตัวหาร - เป็นระยะในการจ่ายเงินปันผลซึ่งมักจะสำคัญ ตัวอย่างเช่นด้วยการแบ่งพื้นday_count % 7ให้วันของสัปดาห์ แต่มีการตัดทอนการแบ่งนี้สำหรับวันที่ก่อนยุค
dan04

0

อันดับแรกฉันจะทำซ้ำว่า modulo b ควรเท่ากับ a - b * (div b) และหากภาษาไม่ได้ให้สิ่งนั้นแสดงว่าคุณกำลังยุ่งอยู่กับคณิตศาสตร์ นิพจน์นั้น a - b * (a div b) จริง ๆ แล้วการประยุกต์ใช้จำนวนมากคำนวณโมดูโล b

มีเหตุผลที่เป็นไปได้บางอย่าง อย่างแรกคือคุณต้องการความเร็วสูงสุดดังนั้น div b จึงถูกกำหนดเป็นสิ่งที่โปรเซสเซอร์ใช้ หากโปรเซสเซอร์ของคุณมีคำสั่ง "div" ดังนั้น div b คือสิ่งที่คำสั่ง div ทำ (ตราบใดที่มันไม่ได้บ้าอย่างสิ้นเชิง)

อย่างที่สองคือคุณต้องการพฤติกรรมทางคณิตศาสตร์บางอย่าง ก่อนอื่นสมมติว่า b> 0 มันค่อนข้างสมเหตุสมผลที่คุณต้องการให้ผลลัพธ์ของ div b ถูกปัดเศษเป็นศูนย์ ดังนั้น 4 div 5 = 0, 9 div 5 = 1, -4 div 5 = -0 = 0, -9 div 5 = -1 สิ่งนี้จะให้ (-a) div b = - (a div b) และ (-a) modulo b = - (a modulo b)

นี่ค่อนข้างสมเหตุสมผล แต่ไม่สมบูรณ์ ตัวอย่างเช่น (a + b) div b = (div b) + 1 ไม่ได้ถือไว้พูดถ้า a = -1 ด้วยค่าคงที่ b> 0 มักจะมีค่าที่เป็นไปได้ (b) สำหรับ div ที่ให้ผลเหมือนกันยกเว้น 2b - 1 ค่า a จาก -b + 1 ถึง b-1 โดยที่ div b เท่ากับ 0 มันก็หมายความว่าโมดูโล b จะเป็นลบหาก a เป็นลบ เราต้องการให้โมดูโล b เป็นจำนวนเสมอในช่วงจาก 0 ถึง b-1

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

เนื่องจากมีข้อขัดแย้งภาษาจะต้องตัดสินใจว่าชุดของข้อดีมีความสำคัญต่อพวกเขาและตัดสินใจตามนั้น

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

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