Mod của số âm đang làm tan chảy não của tôi


187

Tôi đang cố gắng sửa đổi một số nguyên để có được một vị trí mảng để nó sẽ lặp vòng. Làm i % arrayLengthtốt cho số dương nhưng với số âm thì tất cả đều sai.

 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

vì vậy tôi cần thực hiện

int GetArrayIndex(int i, int arrayLength)

như vậy mà

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

Tôi đã làm điều này trước đây nhưng vì một số lý do, nó làm tan chảy bộ não của tôi ngày hôm nay :(


Xem các cuộc thảo luận về mô đun toán học trên math.stackexchange.com/questions/519845/NH
PPC

Câu trả lời:


279

Tôi luôn sử dụng modchức năng của riêng mình , được định nghĩa là

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

Tất nhiên, nếu bạn bận tâm về việc có hai cuộc gọi đến hoạt động mô đun, bạn có thể viết nó dưới dạng

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

hoặc các biến thể của chúng.

Lý do nó hoạt động là "x% m" luôn nằm trong phạm vi [-m + 1, m-1]. Vì vậy, nếu ở tất cả nó là âm, thêm m vào nó sẽ đặt nó trong phạm vi dương mà không thay đổi giá trị modulo m của nó.


7
Lưu ý: để hoàn thành lý thuyết số hoàn chỉnh, bạn có thể muốn thêm một dòng ở trên cùng nói "if (m <0) m = -m;" mặc dù trong trường hợp này, nó không quan trọng vì "mảng" có lẽ luôn luôn dương.
ShreevatsaR

4
Nếu bạn định kiểm tra giá trị của m, bạn cũng nên loại trừ số không.
billpg

6
@RuudLenders: Số Nếu x = -5 và m = 2, sau đó r = x%m-1, sau đó r+m1. Vòng lặp while là không cần thiết. Vấn đề là (như tôi đã viết trong câu trả lời), x%mluôn luôn lớn hơn -m, vì vậy bạn cần thêm mnhiều nhất một lần để làm cho nó tích cực.
ShreevatsaR

4
@dcastro: Tôi làm muốn -12 mod -10 là 8. Đây là quy ước chung nhất trong toán học, rằng nếu chọn một người đại diện rcho amodulo b, sau đó nó là như vậy mà 0 ≤ r <| b |.
ShreevatsaR

8
+1. Tôi không quan tâm bất kỳ ngôn ngữ riêng lẻ nào làm cho một mô-đun âm - 'dư lượng không âm ít nhất' thể hiện tính đều đặn toán học và loại bỏ bất kỳ sự mơ hồ nào.
Brett Hale

78

Xin lưu ý rằng toán tử% của C # và C ++ thực sự KHÔNG phải là modulo, phần còn lại. Công thức cho modulo mà bạn muốn, trong trường hợp của bạn, là:

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

Bạn phải mã hóa lại điều này trong C # (hoặc C ++) nhưng đây là cách bạn có được modulo chứ không phải phần còn lại.


21
"Xin lưu ý rằng toán tử% của C ++ thực sự KHÔNG phải là modulo, nó còn lại." Cảm ơn, bây giờ nó có ý nghĩa, luôn tự hỏi tại sao nó không bao giờ hoạt động đúng với các số âm.
leetNightshade

2
"Xin lưu ý rằng toán tử% của C ++ thực sự KHÔNG phải là modulo, nó là phần còn lại." Tôi không nghĩ điều này là chính xác và tôi không hiểu tại sao modulo lại khác với phần còn lại. Đó là những gì nó cũng nói trên trang Wikipedia Hoạt động Modulo. Chỉ là ngôn ngữ lập trình xử lý các số âm khác nhau. Toán tử modulo trong C # rõ ràng đếm số dư "từ" zero (-9% 4 = -1, vì 4 * -2 là -8 với chênh lệch -1) trong khi định nghĩa khác sẽ coi -9% 4 là +3, bởi vì -4 * 3 là -12, phần còn lại +3 (chẳng hạn như trong chức năng tìm kiếm của Google, không chắc chắn về ngôn ngữ phụ trợ ở đó).
Tyress

17
Tyress, có một sự khác biệt giữa mô-đun và phần còn lại. Ví dụ: -21 mod 4 is 3 because -21 + 4 x 6 is 3. Nhưng -21 divided by 4 gives -5với a remainder of -1. Đối với các giá trị tích cực, không có sự khác biệt. Vì vậy, xin vui lòng thông báo cho mình về những khác biệt. Và đừng tin tưởng Wikipedia tất cả các thời gian :)
Петър Петров

2
Tại sao bất cứ ai cũng muốn sử dụng chức năng còn lại thay vì modulo? Tại sao họ làm %phần còn lại?
Aaron Franke

3
@AaronFranke - đó là một di sản từ các cpus trước đó có phần cứng phân chia để nhanh chóng tạo ra thương số và phần còn lại - và đây là những gì phần cứng đã mang lại cho cổ tức âm. Ngôn ngữ chỉ đơn giản là nhân đôi phần cứng. Hầu hết các lập trình viên đã làm việc với cổ tức tích cực, và bỏ qua điều này. Tốc độ là tối quan trọng.
ToolmakerSteve

15

Thực hiện một dòng %chỉ sử dụng một lần:

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

1
điều này có đúng không vì tôi không thấy nó được chấp nhận bởi bất cứ ai, cũng như bất kỳ bình luận nào cho nó. Ví dụ. mod (-10,6) sẽ trả về 6. Điều đó có đúng không? Có nên trả lại 4 không?
John Demetriou

3
@JohnDemetriou Các số của bạn đều sai: (A) nó sẽ trả về 2 và (B) nó trả về 2; hãy thử chạy mã. Mục (A): để tìm mod(-10, 6)bằng tay, bạn có thể cộng hoặc trừ 6 lần lặp lại cho đến khi câu trả lời nằm trong phạm vi [0, 6). Ký hiệu này có nghĩa là "bao gồm bên trái và độc quyền ở bên phải". Trong trường hợp của chúng tôi, chúng tôi thêm 6 lần, cho 2. Mã khá đơn giản và thật dễ dàng để thấy rằng nó đúng: đầu tiên, nó tương đương với việc cộng / trừ nnhư trên, ngoại trừ việc nó dừng một nđoạn ngắn, nếu tiếp cận từ mặt tiêu cực. Trong trường hợp đó, chúng tôi sửa nó. Có: bình luận :)
Evgeni Sergeev

1
Nhân tiện, đây là một lý do tại sao sử dụng một đơn %có thể là một ý tưởng tốt. Xem bảng Điều gì chi phí trong mã được quản lý trong bài viết Viết mã được quản lý nhanh hơn: Biết những gì chi phí . Sử dụng %tương tự đắt tiền int divđược liệt kê trong bảng: đắt hơn khoảng 36 lần so với cộng hoặc trừ, và đắt hơn khoảng 13 lần so với nhân. Tất nhiên, không có vấn đề gì lớn trừ khi đây là cốt lõi của những gì mã của bạn đang làm.
Evgeni Sergeev

2
Nhưng có phải một %thứ đắt hơn một bài kiểm tra và nhảy, đặc biệt là nếu nó không thể dễ dàng dự đoán?
Medinoc

7

Câu trả lời của ShreevatsaR sẽ không hoạt động trong mọi trường hợp, ngay cả khi bạn thêm "if (m <0) m = -m;", nếu bạn tính đến cổ tức / ước số âm.

Ví dụ: -12 mod -10 sẽ là 8 và nó phải là -2.

Việc triển khai sau đây sẽ hoạt động cho cả cổ tức / ước tính tích cực và tiêu cực và tuân thủ các triển khai khác (cụ thể là Java, Python, Ruby, Scala, Scheme, Javascript và Máy tính của 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;
    }
}

Bộ kiểm tra sử dụng 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 };
        }
    }

Đầu tiên, một modhàm thường được gọi với mô đun dương (lưu ý biến arrayLengthtrong câu hỏi ban đầu đang được trả lời ở đây, có lẽ không bao giờ âm), vì vậy hàm này không thực sự cần được thực hiện để hoạt động cho mô đun âm. (Đó là lý do tại sao tôi đề cập đến việc xử lý mô đun âm trong một nhận xét về câu trả lời của tôi, chứ không phải trong chính câu trả lời.) (Contd ...)
ShreevatsaR

3
(... contd) Thứ hai, phải làm gì cho mô đun âm là vấn đề quy ước. Xem ví dụ Wikipedia . "Thông thường, trong lý thuyết số, phần còn lại luôn luôn được chọn" và đây là cách tôi học nó (trong Lý thuyết số cơ bản của Burton ). Knuth cũng định nghĩa nó theo cách đó (cụ thể, r = a - b floor(a/b)luôn luôn tích cực). Ngay cả trong số các hệ thống máy tính, Pascal và Maple chẳng hạn, định nghĩa nó luôn luôn tích cực.
ShreevatsaR

@ShreevatsaR Tôi biết rằng định nghĩa Euclidian nói rằng kết quả sẽ luôn dương - nhưng tôi có ấn tượng rằng hầu hết các triển khai mod hiện đại sẽ trả về một giá trị trong phạm vi [n + 1, 0] cho ước số âm "n", có nghĩa là -12 mod -10 = -2. Ive đã xem xét Google Calculator , Python , RubyScala và tất cả đều tuân theo quy ước này.
dcastro

Ngoài ra, để thêm vào danh sách: SchemeJavascript
dcastro

1
Một lần nữa, đây vẫn là một đọc tốt. Định nghĩa "luôn luôn tích cực" (câu trả lời của tôi) phù hợp với ALGOL, Dart, Maple, Pascal, Z3, v.v ... "Dấu hiệu của ước số" (câu trả lời này) phù hợp với: APL, COBOL, J, Lua, Mathicala, MS Excel, Perl, Python, R, Ruby, Tcl, v.v ... Cả hai đều không phù hợp với "dấu hiệu cổ tức" như trong: AWK, bash, bc, C99, C ++ 11, C #, D, Eiffel, Erlang, Go, Java , OCaml, PHP, Rust, Scala, Swift, VB, x86, v.v.
ShreevatsaR

6

Thêm một số hiểu biết.

Theo định nghĩa Euclide , kết quả mod phải luôn dương.

Ví dụ:

 int n = 5;
 int x = -3;

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

Đầu ra:

 -1

15
Tôi bối rối ... bạn nói rằng kết quả phải luôn luôn dương, nhưng sau đó liệt kê đầu ra là -1?
Jeff B

@JeffBridgman Tôi đã tuyên bố rằng dựa trên định nghĩa Euclide. `có hai lựa chọn khả dĩ cho phần còn lại, một tiêu cực và tích cực khác, và cũng có hai lựa chọn khả dĩ cho thương số. Thông thường, trong lý thuyết số the positive remainder is always chosen, nhưng ngôn ngữ lập trình chọn tùy thuộc vào ngôn ngữ và các dấu hiệu của a và / hoặc n. [5] Pascal và Algol68 tiêu chuẩn cung cấp phần còn lại dương (hoặc 0) ngay cả cho các ước số âm và một số ngôn ngữ lập trình, chẳng hạn như C90, để lại cho đến khi thực hiện khi n hoặc a âm
Abin

5

So sánh hai câu trả lời chiếm ưu thế

(x%m + m)%m;

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

Không ai thực sự đề cập đến thực tế rằng người đầu tiên có thể ném một OverflowExceptionlúc người thứ hai sẽ không. Thậm chí tệ hơn, với bối cảnh không được kiểm tra mặc định, câu trả lời đầu tiên có thể trả về câu trả lời sai (xem mod(int.MaxValue - 1, int.MaxValue)ví dụ). Vì vậy, câu trả lời thứ hai không chỉ có vẻ nhanh hơn, mà còn đúng hơn.


4

Chỉ cần thêm mô-đun của bạn (mảng Độ dài) vào kết quả âm của% và bạn sẽ ổn.


4

Đối với các nhà phát triển nhận thức hiệu suất cao hơn

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

Một so sánh hiệu suất nhỏ

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

Đối với chi phí hiệu suất của dàn diễn viên để uint có một cái nhìn ở đây


3
Methinks -3 % 10nên là -3 hoặc 7. Vì muốn có kết quả không âm, 7 sẽ là câu trả lời. Việc thực hiện của bạn trả về 3. Bạn nên thay đổi cả hai tham số thành uintvà loại bỏ cast.
Tôi thích Stack Overflow cũ

5
Số học không được gán chỉ tương đương nếu nlà lũy thừa của hai, trong trường hợp đó bạn chỉ cần sử dụng logic và ( (uint)k & (n - 1)), nếu trình biên dịch không làm điều đó cho bạn (trình biên dịch thường đủ thông minh để tìm ra điều này).
j_schultz

2

Tôi thích thủ thuật được trình bày bởi Peter N Lewis chủ đề này : "Nếu n có phạm vi giới hạn, thì bạn có thể nhận được kết quả bạn muốn chỉ bằng cách thêm bội số đã biết của [ước số] lớn hơn giá trị tuyệt đối của tối thiểu. "

Vì vậy, nếu tôi có một giá trị d tính bằng độ và tôi muốn lấy

d % 180f

và tôi muốn tránh các vấn đề nếu d âm tính, thay vào đó tôi chỉ làm điều này:

(d + 720f) % 180f

Điều này giả định rằng mặc dù d có thể âm, nhưng người ta biết rằng nó sẽ không bao giờ âm hơn -720.


2
-1: không đủ chung, (và rất dễ đưa ra giải pháp tổng quát hơn).
Evgeni Sergeev

4
Điều này thực sự rất hữu ích. Khi bạn có phạm vi có ý nghĩa, điều này có thể đơn giản hóa tính toán. trong trường hợp của tôi math.stackexchange.com/questions/2279751/ từ
M.kazem Akhÿ

Chính xác, chỉ sử dụng điều này cho tính toán dayOfWeek (phạm vi đã biết từ -6 đến +6) và nó đã lưu có hai %.
NetMage

@EvgeniSergeev +0 cho tôi: không trả lời câu hỏi OP nhưng có thể hữu ích trong ngữ cảnh cụ thể hơn (nhưng vẫn trong bối cảnh của câu hỏi)
Erdal G.

1

Bạn đang mong đợi một hành vi trái với hành vi được ghi lại của toán tử% trong c # - có thể vì bạn đang mong đợi nó hoạt động theo cách mà nó hoạt động theo ngôn ngữ khác mà bạn quen dùng hơn. Các tài liệu về c # bang (tôi nhấn mạnh):

Đối với các toán hạng của các kiểu số nguyên, kết quả của% b là giá trị được tạo bởi a - (a / b) * b. Dấu của phần còn lại khác không giống với dấu của toán hạng bên trái

Giá trị bạn muốn có thể được tính bằng một bước bổ sung:

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

0

Tất cả các câu trả lời ở đây hoạt động rất tốt nếu ước số của bạn là tích cực, nhưng nó không hoàn toàn đầy đủ. Đây là cách thực hiện của tôi, nó luôn trả về một phạm vi[0, b) , sao cho dấu của đầu ra giống như dấu của ước số, cho phép các ước số âm làm điểm cuối cho phạm vi đầu ra.

PosMod(5, 3)trả 2
PosMod(-5, 3)về 1
PosMod(5, -3)trả -1
PosMod(-5, -3)về trả về-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;
    }

(nơi real_tcó thể là bất kỳ loại số nào)


0

Một dòng thực hiện câu trả lời của dcastro (phù hợp nhất với các ngôn ngữ khác):

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

Nếu bạn muốn tiếp tục sử dụng %toán tử (bạn không thể quá tải toán tử gốc trong 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);
}

Ca sử dụng, cả hai tác phẩm:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);
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.