จำนวนโมดิฟลบกำลังละลายสมองของฉัน


189

ฉันพยายามที่จะแก้ไขจำนวนเต็มเพื่อให้ได้ตำแหน่งอาเรย์เพื่อที่จะวนรอบ การi % arrayLengthทำงานได้ผลดีสำหรับตัวเลขบวก แต่สำหรับตัวเลขลบมันทั้งหมดผิดพลาด

 4 % 3 == 1
 3 % 3 == 0
 2 % 3 == 2
 1 % 3 == 1
 0 % 3 == 0
-1 % 3 == -1
-2 % 3 == -2
-3 % 3 == 0
-4 % 3 == -1

ดังนั้นฉันต้องการการดำเนินการของ

int GetArrayIndex(int i, int arrayLength)

ดังนั้น

GetArrayIndex( 4, 3) == 1
GetArrayIndex( 3, 3) == 0
GetArrayIndex( 2, 3) == 2
GetArrayIndex( 1, 3) == 1
GetArrayIndex( 0, 3) == 0
GetArrayIndex(-1, 3) == 2
GetArrayIndex(-2, 3) == 1
GetArrayIndex(-3, 3) == 0
GetArrayIndex(-4, 3) == 2

ฉันเคยทำมาก่อน แต่ด้วยเหตุผลบางอย่างมันทำให้สมองฉันละลายในวันนี้ :(


ดูการอภิปรายเกี่ยวกับคณิตศาสตร์โมดูลัสบนmath.stackexchange.com/questions/519845/...
PPC

blogs.msdn.microsoft.com/ericlippert/2011/12/05/…เป็นบทความที่ดี
Samra

คำตอบ:


281

ฉันมักจะใช้modฟังก์ชั่นของตัวเองหมายถึง

int mod(int x, int m) {
    return (x%m + m)%m;
}

แน่นอนถ้าคุณกังวลเกี่ยวกับการเรียกสองสายไปยังการดำเนินการโมดูลัสคุณสามารถเขียนมันเป็น

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

หรือหลากหลายรูปแบบ

เหตุผลที่ใช้ได้คือ "x% m" อยู่ในช่วง [-m + 1, m-1] เสมอ ดังนั้นหากเป็นลบการเพิ่ม m ลงไปจะอยู่ในช่วงบวกโดยไม่เปลี่ยนค่าโมดูโล m


7
หมายเหตุ: สำหรับความสมบูรณ์เชิงตัวเลข - เชิงทฤษฎีคุณอาจต้องการเพิ่มบรรทัดที่ด้านบนว่า "ถ้า (m <0) m = -m;" แม้ว่าในกรณีนี้มันไม่สำคัญว่า "arrayLength" น่าจะเป็นบวกเสมอ
ShreevatsaR

4
หากคุณจะตรวจสอบค่าของ m คุณควรยกเว้นศูนย์ด้วย
billpg

6
@RuudLenders: เลขที่ถ้า x = -5 และ m = 2 แล้วr = x%mคือ-1หลังจากที่เป็นr+m 1ขณะที่ไม่จำเป็นต้องวนรอบ ประเด็นก็คือ (อย่างที่ฉันเขียนไว้ในคำตอบ) x%mมักจะยิ่งใหญ่กว่าเสมอ-mดังนั้นคุณต้องเพิ่มอย่างmน้อยหนึ่งครั้งเพื่อทำให้เป็นบวก
ShreevatsaR

4
@dcastro: ฉันไม่ต้องการ -12 mod -10 ถึงจะ 8. นี่คือแผนการที่พบมากที่สุดในคณิตศาสตร์ว่าถ้าการเลือกตัวแทนrสำหรับaโมดูโลbแล้วมันเป็นเช่นที่ 0 ≤ R <| ข |
ShreevatsaR

8
+1 ฉันไม่สนใจว่าภาษาใดที่แต่ละคนทำเพื่อโมดูลัสเชิงลบ - 'สารตกค้างที่ไม่ใช่ลบน้อยที่สุด' แสดงถึงความสม่ำเสมอทางคณิตศาสตร์และกำจัดความคลุมเครือใด ๆ
Brett Hale

80

โปรดทราบว่าโอเปอเรเตอร์% C ของ C และ C ++ นั้นไม่ใช่โมดูโล สูตรสำหรับ modulo ที่คุณต้องการในกรณีของคุณคือ:

float nfmod(float a,float b)
{
    return a - b * floor(a / b);
}

คุณต้องบันทึกสิ่งนี้ใหม่ใน C # (หรือ C ++) แต่นี่เป็นวิธีที่คุณได้รับ modulo ไม่ใช่ส่วนที่เหลือ


21
"โปรดทราบว่าโอเปอเรเตอร์ของ% C + + นั้นไม่ใช่มอดูโลจริง ๆ แล้วมันยังเหลืออยู่" ขอบคุณตอนนี้มันสมเหตุสมผลแล้วสงสัยอยู่เสมอว่าทำไมมันไม่ทำงานอย่างถูกต้องกับจำนวนลบ
leetNightshade

2
"โปรดทราบว่าโอเปอเรเตอร์ของ C ++ นั้นไม่ใช่มอดูโลจริง ๆ มันเหลืออยู่" ฉันไม่คิดว่ามันถูกต้องและฉันไม่เห็นว่าทำไมโมดูโล่ถึงแตกต่างจากส่วนที่เหลือ นั่นคือสิ่งที่กล่าวไว้ในหน้า Wikipedia Operation เป็นเพียงว่าภาษาการเขียนโปรแกรมจัดการกับจำนวนลบต่างกัน โอเปอเรเตอร์แบบโมดูโลใน C # จะนับส่วนที่เหลืออย่างชัดเจน "จาก" ศูนย์ (-9% 4 = -1, เพราะ 4 * -2 คือ -8 กับความแตกต่างของ -1) ในขณะที่นิยามอื่นจะพิจารณา -9% 4 เป็น +3 เพราะ -4 * 3 คือ -12, ส่วนที่เหลือ +3 (เช่นในฟังก์ชั่นการค้นหาของ Google, ไม่แน่ใจว่ามีภาษาแบ็คเอนด์อยู่ที่นั่น)
ทรราช

18
ทรราชมีความแตกต่างระหว่างโมดูลัสและส่วนที่เหลือ ตัวอย่างเช่น: -21 mod 4 is 3 because -21 + 4 x 6 is 3. แต่-21 divided by 4 gives -5ด้วย a remainder of -1. สำหรับค่าบวกไม่มีความแตกต่าง ดังนั้นโปรดแจ้งตัวเองเกี่ยวกับความแตกต่างเหล่านี้ และอย่าไว้ใจ Wikipedia ตลอดเวลา :)
ПетърПетров

2
ทำไมทุกคนต้องการใช้ฟังก์ชั่นส่วนที่เหลือแทนโมดูโล ทำไมพวกเขาถึงทำ%ส่วนที่เหลือ?
Aaron Franke

4
@AaronFranke - เป็นมรดกจากซีพียูก่อนหน้านี้ที่มีส่วนฮาร์ดแวร์ที่จะผลิตความฉลาดและส่วนที่เหลือ - และนี่คือสิ่งที่ฮาร์ดแวร์ที่ได้รับเงินปันผลเชิงลบ ภาษาสะท้อนฮาร์ดแวร์เพียงอย่างเดียว โปรแกรมเมอร์ส่วนใหญ่ทำงานด้วยการจ่ายปันผลเป็นบวกและไม่สนใจเรื่องนี้ ความเร็วเป็นสิ่งสำคัญยิ่ง
ToolmakerSteve

16

การใช้งานแบบบรรทัด%เดียวโดยใช้เพียงครั้งเดียว:

int mod(int k, int n) {  return ((k %= n) < 0) ? k+n : k;  }

1
ถูกต้องหรือไม่ ตามที่ฉันไม่เห็นมันเป็นที่ยอมรับโดยทุกคนหรือความคิดเห็นใด ๆ ตัวอย่างเช่น. mod (-10,6) จะส่งคืน 6. ถูกต้องไหม? มันควรจะไม่กลับ 4?
John Demetriou

3
@JohnDemetriou ตัวเลขของคุณทั้งคู่ผิด: (A) มันควรจะคืน 2 และ (B) มันจะคืน 2 ลองเรียกใช้รหัส รายการ (A): ในการค้นหาmod(-10, 6)ด้วยมือคุณทั้งเพิ่มหรือลบ 6 [0, 6)ซ้ำจนกว่าคำตอบอยู่ในช่วง สัญกรณ์นี้หมายถึง "รวมทางซ้ายและพิเศษทางด้านขวา" ในกรณีของเราเราเพิ่ม 6 สองครั้งโดยให้ 2 โค้ดค่อนข้างง่ายและง่ายที่จะเห็นว่าถูกต้อง: อันดับแรกมันเทียบเท่ากับการเพิ่ม / ลบnดังที่กล่าวมาข้างต้นยกเว้นว่าจะหยุดnสั้น ๆหนึ่งถ้าเข้าใกล้จาก ด้านลบ ในกรณีนั้นเราจะแก้ไข ที่นั่น: ความเห็น :)
Evgeni Sergeev

1
นี่เป็นเหตุผลว่าทำไมการใช้ซิงเกิ้ล%อาจเป็นความคิดที่ดี ดูตารางอะไรสิ่งที่มีค่าใช้จ่ายในการจัดการรหัสในบทความเขียนได้เร็วขึ้นการจัดการรหัสสินค้า: รู้ว่าสิ่งที่สิ่งที่ค่าใช้จ่าย การใช้%มีราคาแพงคล้ายกันในint divรายการ: ตารางมีราคาแพงกว่าการเพิ่มหรือลบประมาณ 36 เท่าและราคาแพงกว่าการคูณประมาณ 13 เท่า แน่นอนว่าไม่ใช่เรื่องใหญ่หากนี่เป็นหัวใจสำคัญของโค้ดของคุณ
Evgeni Sergeev

2
แต่มี%ราคาแพงกว่าการทดสอบและการกระโดดเพียงครั้งเดียวโดยเฉพาะอย่างยิ่งหากไม่สามารถคาดการณ์ได้ง่าย?
Medinoc

6

คำตอบของ ShreevatsaR จะไม่ทำงานในทุกกรณีแม้ว่าคุณจะเพิ่ม "ถ้า (m <0) m = -m;" หากคุณบัญชีสำหรับเงินปันผล / ตัวลบ

ตัวอย่างเช่น -12 mod -10 จะเท่ากับ 8 และควรเป็น -2

การนำไปใช้งานต่อไปนี้จะใช้ได้ทั้งกับตัวแบ่งเงินปันผลและตัวแบ่งผลบวกและทางลบและเป็นไปตามการปรับใช้อื่น ๆ (เช่น Java, Python, Ruby, Scala, Scheme, Javascript และเครื่องคำนวณของ Google):

internal static class IntExtensions
{
    internal static int Mod(this int a, int n)
    {
        if (n == 0)
            throw new ArgumentOutOfRangeException("n", "(a mod 0) is undefined.");

        //puts a in the [-n+1, n-1] range using the remainder operator
        int remainder = a%n;

        //if the remainder is less than zero, add n to put it in the [0, n-1] range if n is positive
        //if the remainder is greater than zero, add n to put it in the [n-1, 0] range if n is negative
        if ((n > 0 && remainder < 0) ||
            (n < 0 && remainder > 0))
            return remainder + n;
        return remainder;
    }
}

ชุดทดสอบโดยใช้ xUnit:

    [Theory]
    [PropertyData("GetTestData")]
    public void Mod_ReturnsCorrectModulo(int dividend, int divisor, int expectedMod)
    {
        Assert.Equal(expectedMod, dividend.Mod(divisor));
    }

    [Fact]
    public void Mod_ThrowsException_IfDivisorIsZero()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => 1.Mod(0));
    }

    public static IEnumerable<object[]> GetTestData
    {
        get
        {
            yield return new object[] {1, 1, 0};
            yield return new object[] {0, 1, 0};
            yield return new object[] {2, 10, 2};
            yield return new object[] {12, 10, 2};
            yield return new object[] {22, 10, 2};
            yield return new object[] {-2, 10, 8};
            yield return new object[] {-12, 10, 8};
            yield return new object[] {-22, 10, 8};
            yield return new object[] { 2, -10, -8 };
            yield return new object[] { 12, -10, -8 };
            yield return new object[] { 22, -10, -8 };
            yield return new object[] { -2, -10, -2 };
            yield return new object[] { -12, -10, -2 };
            yield return new object[] { -22, -10, -2 };
        }
    }

ประการแรกmodฟังก์ชั่นมักจะเรียกว่ามีโมดูลัสเชิงบวก (หมายเหตุตัวแปรarrayLengthในคำถามเดิมที่มีการตอบที่นี่ซึ่งสันนิษฐานว่าไม่เคยติดลบ) ดังนั้นฟังก์ชั่นไม่จำเป็นต้องทำเพื่อให้โมดูลัสเชิงลบ (นั่นคือเหตุผลที่ฉันพูดถึงการรักษาโมดูลัสเชิงลบในความคิดเห็นในคำตอบของฉันไม่ได้อยู่ในคำตอบของตัวเอง) (ต่อ ... )
ShreevatsaR

3
(... ต่อ) ประการที่สองสิ่งที่ต้องทำเพื่อโมดูลัสเชิงลบเป็นเรื่องของการประชุม ดูเช่นวิกิพีเดีย "โดยปกติแล้วในทฤษฎีเชิงตัวเลขส่วนที่เหลือเชิงบวกจะถูกเลือกเสมอ" และนี่คือวิธีที่ฉันเรียนรู้เช่นกัน (ในทฤษฎีหมายเลขเบื้องต้นของเบอร์ตัน) Knuth ยังกำหนดด้วยวิธีนั้น (โดยเฉพาะr = a - b floor(a/b)เป็นบวกอยู่เสมอ) แม้แต่ในระบบคอมพิวเตอร์เช่น Pascal และ Maple ก็ให้นิยามว่าเป็นบวกเสมอ
ShreevatsaR

@ShreevatsaR ฉันรู้ว่าคำจำกัดความของยูคลีนิคระบุว่าผลลัพธ์จะเป็นผลบวกเสมอ - แต่ฉันรู้สึกว่าการใช้งาน mod ที่ทันสมัยที่สุดจะส่งคืนค่าในช่วง [n + 1, 0] สำหรับตัวหารเชิงลบ "n" ซึ่งหมายความว่า -12 mod -10 = -2 ฉันมองเข้าไปในGoogle Calculator , Python , RubyและScalaและพวกเขาทั้งหมดปฏิบัติตามอนุสัญญานี้
dcastro

นอกจากนี้หากต้องการเพิ่มในรายการ: SchemeและJavascript
dcastro

1
อีกครั้งนี้ก็ยังคงเป็นอ่านที่ดี คำนิยาม "คำตอบบวกเสมอ" (คำตอบของฉัน) สอดคล้องกับ ALGOL, Dart, Maple, Pascal, Z3 เป็นต้นคำว่า "เครื่องหมายของตัวหาร" (คำตอบนี้) สอดคล้องกับ: APL, COBOL, J, Lua, Mathematica, MS Excel, Perl, Python, R, Ruby, Tcl และอื่น ๆทั้งสองจะไม่สอดคล้องกับ "สัญลักษณ์การจ่ายเงินปันผล" ใน: AWK, bash, bc, C99, C ++ 11, C #, D, Eiffel, Erlang, Go, Java , OCaml, PHP, สนิม, สกาล่า, สวิฟท์, VB, x86 แอสเซมบลี ฯลฯ ฉันไม่เห็นวิธีที่คุณสามารถอ้างสิทธิ์ในการประชุมหนึ่งครั้งคือ "ถูกต้อง" และอื่น ๆ "ผิด"
ShreevatsaR

6

การเพิ่มความเข้าใจ

โดยนิยามแบบยุคลิดผลลัพธ์ mod จะต้องเป็นบวกเสมอ

Ex:

 int n = 5;
 int x = -3;

 int mod(int n, int x)
 {
     return ((n%x)+x)%x;
 }

เอาท์พุท:

 -1

15
ฉันสับสน ... คุณบอกว่าผลลัพธ์ควรเป็นค่าบวกเสมอ แต่จากนั้นแสดงรายการผลลัพธ์เป็น-1อย่างไร
Jeff B

@JeffBridgman ฉันได้ระบุว่าตามคำจำกัดความแบบยุคลิด `มีสองตัวเลือกที่เป็นไปได้สำหรับส่วนที่เหลือเป็นค่าลบหนึ่งค่าและค่าบวกอื่น ๆ และยังมีสองตัวเลือกที่เป็นไปได้สำหรับความฉลาดทาง โดยทั่วไปในทฤษฎีตัวเลขthe positive remainder is always chosenแต่ภาษาการเขียนโปรแกรมเลือกขึ้นอยู่กับภาษาและเครื่องหมายของ a และ / หรือ n. [5] Pascal มาตรฐานและ Algol68 ให้ส่วนที่เหลือเป็นบวก (หรือ 0) แม้สำหรับตัวหารเชิงลบและภาษาการเขียนโปรแกรมบางอย่างเช่น C90 ปล่อยให้มันขึ้นอยู่กับการใช้งานเมื่อ n หรือ a เป็นลบ "
Abin

5

เปรียบเทียบสองคำตอบที่เด่น

(x%m + m)%m;

และ

int r = x%m;
return r<0 ? r+m : r;

ไม่มีใครพูดถึงความจริงที่ว่าคนแรกอาจโยนOverflowExceptionในขณะที่คนที่สองจะไม่ ยิ่งแย่ไปกว่านั้นด้วยบริบทที่ไม่ได้ตรวจสอบเริ่มต้นคำตอบแรกอาจส่งคืนคำตอบที่ไม่ถูกต้อง (ดูmod(int.MaxValue - 1, int.MaxValue)ตัวอย่าง) ดังนั้นคำตอบที่สองไม่เพียง แต่ดูเหมือนว่าจะเร็วขึ้น แต่ยังถูกต้องกว่า


4

เพียงเพิ่มโมดูลัสของคุณ (arrayLength) ไปยังผลลัพธ์เชิงลบของ% และคุณจะไม่เป็นไร


4

สำหรับ devs การรับรู้ประสิทธิภาพมากขึ้น

uint wrap(int k, int n) ((uint)k)%n

การเปรียบเทียบประสิทธิภาพเล็กน้อย

Modulo: 00:00:07.2661827 ((n%x)+x)%x)
Cast:   00:00:03.2202334 ((uint)k)%n
If:     00:00:13.5378989 ((k %= n) < 0) ? k+n : k

สำหรับประสิทธิภาพการทำงานของการหล่อเพื่อ uint ดูที่นี่


3
คิดว่า-3 % 10ควรเป็น -3 หรือ 7 เนื่องจากต้องการผลลัพธ์ที่ไม่เป็นลบ 7 จะเป็นคำตอบ การใช้งานของคุณจะส่งกลับ 3 คุณควรเปลี่ยนพารามิเตอร์ทั้งสองเป็นuintและลบตัวละคร
ฉันชอบ Stack Overflow เก่า

5
การคำนวณทางคณิตศาสตร์ที่ไม่ได้ลงนามนั้นจะเทียบเท่าได้หากnเป็นกำลังสองซึ่งในกรณีนี้คุณสามารถใช้ตรรกะและ ((uint)k & (n - 1) ) แทนได้หากคอมไพเลอร์ไม่ได้ทำเพื่อคุณ (คอมไพเลอร์มักฉลาดพอที่จะเข้าใจเรื่องนี้)
j_schultz

2

ฉันชอบเคล็ดลับที่นำเสนอโดย Peter N Lewis ในหัวข้อนี้ : "ถ้า n มีช่วงที่ จำกัด คุณสามารถได้ผลลัพธ์ที่คุณต้องการเพียงแค่เพิ่มค่าคงที่ที่รู้จักกันหลายตัวของ [ตัวหาร] ซึ่งมากกว่าค่าสัมบูรณ์ของ ขั้นต่ำ."

ดังนั้นถ้าฉันมีค่าdที่มีหน่วยเป็นองศาและฉันอยากรับ

d % 180f

และฉันต้องการหลีกเลี่ยงปัญหาหากdเป็นลบจากนั้นฉันจะทำสิ่งนี้แทน:

(d + 720f) % 180f

นี่ถือว่าสมมติว่าแม้ว่าdอาจเป็นลบ แต่ก็เป็นที่รู้กันว่ามันจะไม่เป็นลบมากกว่า -720


2
-1: ไม่ธรรมดาพอ (และมันง่ายมากที่จะให้คำตอบทั่วไปมากขึ้น)
Evgeni Sergeev

4
มันมีประโยชน์มากจริงๆ เมื่อคุณมีช่วงที่มีความหมายสิ่งนี้สามารถทำให้การคำนวณง่ายขึ้น ในกรณีของฉันmath.stackexchange.com/questions/2279751/ …
M.kazem Akhgary

ว่าเพียงแค่ใช้วิธีนี้ในการคำนวณวันในสัปดาห์ (ช่วงที่รู้จักกันดีของ -6 +6) %และบันทึกไว้มีสอง
NetMage

@EvgeniSergeev +0 สำหรับฉัน: ไม่ตอบคำถาม OP แต่สามารถเป็นประโยชน์ในบริบทที่เฉพาะเจาะจงมากขึ้น (แต่ยังอยู่ในบริบทของคำถาม)
Erdal G.

1

คุณคาดหวังว่าพฤติกรรมที่ตรงกันข้ามกับพฤติกรรมที่เป็นเอกสารของตัวดำเนินการ% ใน c # - อาจเป็นเพราะคุณคาดหวังให้มันทำงานในลักษณะที่ใช้งานได้ในภาษาอื่นที่คุณคุ้นเคยมากกว่า เอกสารใน C # รัฐ (เหมืองเน้น):

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

ค่าที่คุณต้องการสามารถคำนวณได้ด้วยขั้นตอนพิเศษหนึ่งขั้น:

int GetArrayIndex(int i, int arrayLength){
    int mod = i % arrayLength;
    return (mod>=0) : mod ? mod + arrayLength;
}

1

การใช้งานบรรทัดเดียวของคำตอบของdcastro (สอดคล้องกับภาษาอื่นมากที่สุด):

int Mod(int a, int n)
{
    return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
}

หากคุณต้องการใช้%โอเปอเรเตอร์ต่อไป (คุณไม่สามารถโอเวอร์โหลดโอเปอร์เรเตอร์ใน C # ได้):

public class IntM
{
    private int _value;

    private IntM(int value)
    {
        _value = value;
    }

    private static int Mod(int a, int n)
    {
        return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
    }

    public static implicit operator int(IntM i) => i._value;
    public static implicit operator IntM(int i) => new IntM(i);
    public static int operator %(IntM a, int n) => Mod(a, n);
    public static int operator %(int a, IntM n) => Mod(a, n);
}

กรณีใช้งานทั้งสอง:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);

0

คำตอบทั้งหมดที่นี่ใช้งานได้ดีถ้าตัวหารของคุณเป็นค่าบวก แต่ยังไม่สมบูรณ์ นี่คือการใช้งานของฉันซึ่งมักจะให้ผลตอบแทนในช่วง[0, b)เสมอเช่นสัญญาณของเอาต์พุตนั้นเหมือนกับสัญญาณของตัวหารที่อนุญาตให้ตัวหารเชิงลบเป็นจุดสิ้นสุดของช่วงเอาต์พุต

PosMod(5, 3)ผล2
PosMod(-5, 3)ตอบแทน1
PosMod(5, -3)ผล-1
PosMod(-5, -3)ตอบแทน-2

    /// <summary>
    /// Performs a canonical Modulus operation, where the output is on the range [0, b).
    /// </summary>
    public static real_t PosMod(real_t a, real_t b)
    {
        real_t c = a % b;
        if ((c < 0 && b > 0) || (c > 0 && b < 0)) 
        {
            c += b;
        }
        return c;
    }

( real_tสามารถเป็นประเภทใดก็ได้)

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