Cách viết mã toán tử modulo (%) trong C / C ++ / Obj-C xử lý số âm


86

Một trong những thú cưng của tôi ghét các ngôn ngữ có nguồn gốc từ C (với tư cách là một nhà toán học) là

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

Giải pháp tốt nhất là gì?

C ++ cho phép khả năng tạo khuôn mẫu và nạp chồng toán tử, nhưng cả hai điều này đều là vùng nước âm u đối với tôi. những ví dụ đã nhận được một cách biết ơn.


1
Tôi không nghĩ rằng đây là "bản sao" của stackoverflow.com/questions/828092/… theo định nghĩa chính thức. Không đúng khi câu trả lời của câu hỏi này có thể được hợp nhất với câu hỏi đó, bởi vì câu hỏi này chỉ hỏi về mô đun, không phải phân chia. Nhưng tôi nghĩ câu hỏi này được bao phủ bởi câu hỏi đó, vì vậy nó gần gũi. Câu trả lời của tôi đã có rồi, FWIW.
Steve Jessop

Có lẽ luồng đó nên được tách ra, vì nó hỏi hai câu hỏi riêng biệt. cách tốt nhất để làm điều đó có thể là hỏi lại câu hỏi bộ phận một cách riêng biệt và sau đó hướng nó đến câu trả lời đó. Tôi sẽ để nó cho một người hiểu cơ chế của trang web này tốt hơn.
P i

3
@Pi được %cho là modulo ... đó là phần còn lại .
obataku

1
Đây là một chủ đề khác mà đây là "bản sao" của: stackoverflow.com/questions/1082917/… Chỉ để tham khảo về %vấn đề này .
leetNightshade

Nếu bạn chỉ chia quyền hạn của hai sau đó nó có thể là một ý tưởng tốt hơn để sử dụng và:(-1) & 8 == 7
Henricus V.

Câu trả lời:


74

Trước hết, tôi muốn lưu ý rằng bạn thậm chí không thể dựa vào thực tế rằng (-1) % 8 == -1. điều duy nhất bạn có thể dựa vào đó là (x / y) * y + ( x % y) == x. Tuy nhiên, phần còn lại có âm hay không là do việc thực hiện xác định .

Bây giờ tại sao sử dụng các mẫu ở đây? Quá tải cho int và longs sẽ xảy ra.

int mod (int a, int b)
{
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

và bây giờ bạn có thể gọi nó như mod (-1,8) và nó sẽ có vẻ là 7.

Chỉnh sửa: Tôi tìm thấy một lỗi trong mã của mình. Nó sẽ không hoạt động nếu b là âm. Vì vậy, tôi nghĩ điều này tốt hơn:

int mod (int a, int b)
{
   if(b < 0) //you can check for b == 0 separately and do what you want
     return -mod(-a, -b);   
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

Tham khảo: C ++ 03 đoạn 5.6 khoản 4:

Toán tử nhị phân / sinh ra thương số và toán tử nhị phân% sinh ra phần còn lại từ phép chia biểu thức đầu tiên cho biểu thức thứ hai. Nếu toán hạng thứ hai của / hoặc% bằng 0 thì hành vi không được xác định; ngược lại (a / b) * b + a% b bằng a. Nếu cả hai toán hạng đều không âm thì phần còn lại là không âm; nếu không, dấu hiệu của phần còn lại được xác định thực thi .


2
@Ohmu: Vâng, đó là tiêu chuẩn C ++. <trích dẫn> Đối với toán hạng tích phân, toán tử / mang lại thương số đại số với bất kỳ phần phân số nào bị loại bỏ; nếu thương số a / b có thể biểu diễn trong kiểu kết quả, (a / b) * b + a% b bằng a. </quote>
Ben Voigt

5
-1. Đã 11 năm kể từ khi điều này được xác định. ISO 9899: 1999 đã định nghĩa nó, và không may đã chọn định nghĩa không tốt.
R .. GitHub DỪNG TRỢ GIÚP ICE

3
@Armen: Bạn thuận tiện xóa chú thích cuối trang <quote> ... phép chia số nguyên tuân theo các quy tắc được xác định trong tiêu chuẩn ISO Fortran, ISO / IEC 1539: 1991, trong đó thương số luôn được làm tròn về 0 </quote>. C ++ mới nâng cấp tiêu chuẩn hành vi này từ "ưa thích" để bắt buộc, giống như Fortran và C.
Ben Voigt

2
@Armen: Thông số cũ bị hỏng, nhưng sự cố bị hỏng khác với vấn đề ký hiệu, và rất dễ bỏ sót cho đến khi bạn nhìn vào từ mới. C ++ 03 không có "nếu thương số a / b có thể biểu diễn trong kiểu kết quả", điều này gây ra vấn đề cho INT_MIN / -1(trên hai lần triển khai bổ sung). Theo thông số cũ, -32768 % -1có thể phải đánh giá đến -65536(cũng không nằm trong phạm vi của loại 16 bit, yuck!) Để danh tính được giữ.
Ben Voigt

1
re "Tuy nhiên, phần còn lại có âm hay không là do việc thực thi xác định.", C ++ 11 đảm bảo rằng phép chia số nguyên làm tròn về phía 0.
Cheers and hth. - Alf

12

Đây là một hàm C xử lý số nguyên dương HOẶC số nguyên âm HOẶC giá trị phân số cho cả hai hàm thao tác

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

Đây chắc chắn là giải pháp thanh lịch nhất từ ​​quan điểm toán học. Tuy nhiên, tôi không chắc liệu nó có mạnh mẽ trong việc xử lý số nguyên hay không. Đôi khi lỗi dấu phẩy động xuất hiện khi chuyển đổi int -> fp -> int.

Tôi đang sử dụng mã này cho các không phải int và một hàm riêng cho int.

LƯU Ý: cần bẫy N = 0!

Mã người kiểm tra:

#include <math.h>
#include <stdio.h>

float mod(float a, float N)
{
    float ret = a - N * floor (a / N);

    printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);

    return ret;
}

int main (char* argc, char** argv)
{
    printf ("fmodf(-10.2, 2.0) = %f.1  == FAIL! \n\n", fmodf(-10.2, 2.0));

    float x;
    x = mod(10.2f, 2.0f);
    x = mod(10.2f, -2.0f);
    x = mod(-10.2f, 2.0f);
    x = mod(-10.2f, -2.0f);

    return 0;
}

(Lưu ý: Bạn có thể biên dịch và chạy nó ngay từ CodePad: http://codepad.org/UOgEqAMA )

Đầu ra:

fmodf (-10.2, 2.0) = -0.20 == FAIL!

10,2 mod 2.0 = 0,2
10,2 mod -2,0 = -1,8
-10,2 mod 2.0 = 1,8
-10,2 mod -2.0 = -0,2


Thật không may, điều này không hoạt động với số nguyên. Chúng sẽ cần được chuyển đổi thành dấu phẩy động trước khi chia để cho phép bạn sử dụng floor(). Ngoài ra, bạn có thể mất độ chính xác khi chuyển đổi thành float: Hãy thử (float)1000000001/3, bạn sẽ ngạc nhiên với kết quả!
cmaster - phục hồi monica

9

Tôi vừa nhận thấy rằng Bjarne Stroustrup gắn nhãn %là toán tử phần dư , không phải toán tử mô đun.

Tôi dám cá rằng đây là tên chính thức của nó trong thông số kỹ thuật ANSI C & C ++ và việc lạm dụng thuật ngữ đã len lỏi vào. Có ai biết điều này cho sự thật không?

Nhưng nếu trường hợp này xảy ra thì hàm fmodf () của C (và có thể là những hàm khác) rất sai lầm. chúng phải được gắn nhãn fremf (), v.v.


1
Tiêu chuẩn C11 ( chính xác là dự thảo công khai cuối cùng ) đề cập đến "modulo" sáu lần, nhưng chỉ liên quan đến đại diện của các loại khác nhau. Không một lần nó đề cập đến "modulo" trong mối quan hệ với toán tử phần dư ( %).
Nisse Engström

7

Hàm tổng quát đơn giản nhất để tìm modulo dương sẽ là hàm này- Nó sẽ hoạt động trên cả giá trị âm và dương của x.

int modulo(int x,int N){
    return (x % N + N) %N;
}

6

Đối với số nguyên, điều này là đơn giản. Cứ làm đi

(((x < 0) ? ((x % N) + N) : x) % N)

trong đó tôi cho rằng điều đó Nlà tích cực và có thể đại diện trong loại x. Trình biên dịch yêu thích của bạn sẽ có thể tối ưu hóa điều này, sao cho nó kết thúc chỉ trong một thao tác mod trong trình lắp ráp.


3
Không hoạt động: cho int x=-9001; unsigned int N=2000;nó mang lại cho 2295, không 999.
Hubert Kario

1
@HubertKario Có thể kiểm tra lại? Không có chuyện modulo 2000 gì đó cho 2295, chắc hẳn bạn đã nhầm.
sam hocevar

2
@SamHocevar: Tôi nghĩ vấn đề ở đây là các quy tắc thăng hạng số nguyên C kỳ lạ. ký kết thúc đẩy để unsigned và thúc đẩy một tiêu cực ký số nguyên giá trị để gọi unsigned hành vi undefined trong C.
datenwolf

1
Tôi tin rằng một hình thức đơn giản hơn nhiều (và hiệu quả hơn) sẽ là: (x < 0) ? (x % N + N) : (x % N).
Chris Nolet

3

Giải pháp tốt nhất ¹ cho một nhà toán học là sử dụng Python.

Việc nạp chồng toán tử C ++ ít liên quan đến nó. Bạn không thể nạp chồng các toán tử cho các kiểu cài sẵn. Những gì bạn muốn chỉ đơn giản là một chức năng. Tất nhiên, bạn có thể sử dụng C ++ templating để triển khai hàm đó cho tất cả các kiểu liên quan chỉ với 1 đoạn mã.

Thư viện C tiêu chuẩn cung cấp fmod, nếu tôi nhớ lại tên một cách chính xác, cho các loại dấu phẩy động.

Đối với số nguyên, bạn có thể xác định một mẫu hàm C ++ luôn trả về phần dư không âm (tương ứng với phép chia Euclidian) là ...

#include <stdlib.h>  // abs

template< class Integer >
auto mod( Integer a, Integer b )
    -> Integer
{
    Integer const r = a%b;
    return (r < 0? r + abs( b ) : r);
}

... và chỉ viết mod(a, b)thay vì a%b.

Ở đây kiểu Integercần phải là kiểu số nguyên có dấu.

Nếu bạn muốn hành vi toán học phổ biến trong đó dấu của phần dư giống với dấu của số chia, thì bạn có thể làm ví dụ:

template< class Integer >
auto floor_div( Integer const a, Integer const b )
    -> Integer
{
    bool const a_is_negative = (a < 0);
    bool const b_is_negative = (b < 0);
    bool const change_sign  = (a_is_negative != b_is_negative);

    Integer const abs_b         = abs( b );
    Integer const abs_a_plus    = abs( a ) + (change_sign? abs_b - 1 : 0);

    Integer const quot = abs_a_plus / abs_b;
    return (change_sign? -quot : quot);
}

template< class Integer >
auto floor_mod( Integer const a, Integer const b )
    -> Integer
{ return a - b*floor_div( a, b ); }

… Với cùng một ràng buộc Integer, rằng đó là kiểu có chữ ký.


¹ Vì phép chia số nguyên của Python làm tròn về phía âm vô cùng.


mã của bạn dường như có lỗi giống như lỗi của tôi trước khi tôi chỉnh sửa. Nếu b là âm thì sao? :)
Armen Tsirunyan

1
@Armen: cảm ơn! nhưng tôi quá lười để chỉnh sửa chỉ vì điều đó ... :-)
Cheers and hth. - Alf

@ArmenTsirunyan: rkết quả phải là a= r + b*(a/b)true. bất kể phép chia số nguyên được thực hiện như thế nào, b*somethinglà bội số của b. điều này làm cho rmột kết quả mô-đun hợp lệ ngay cả khi âm tính. bạn có thể thêm abs ( b) vào nó và nó sẽ vẫn là một kết quả modulo hợp lệ.
Chúc mừng và hth. - Alf

2
@downvoters: Câu trả lời này vẫn đúng, trong khi "giải pháp" đã chọn hiện chứa bình luận không chính xác do các đảm bảo mới trong C ++ 11. Thật là trớ trêu khi từ chối một câu trả lời vẫn đúng. Không có lý do gì mà người ta phải cho rằng ít nhất 2 người có liên quan, với mức độ thiếu hiểu biết gần như tuyệt đối, đã đọc phần bình luận của câu hỏi này và phản đối đồng tình. Vui lòng giải thích phiếu phản đối của bạn.
Chúc mừng và hth. - Alf

1
Kết quả mong muốn về mặt toán học là phần dư bằng 0 hoặc có cùng dấu với số chia (mẫu số). Nếu số chia là số âm, thì phần còn lại phải bằng 0 hoặc âm. Việc triển khai C / C ++ dẫn đến phần còn lại bằng 0 hoặc có cùng dấu với số bị chia (tử số).
rcgldr

2

Ồ, tôi cũng ghét thiết kế% cho cái này ....

Bạn có thể chuyển đổi cổ tức thành không dấu theo cách như:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

nơi bù đắp gần nhất với (-INT_MIN) bội số của mô-đun, vì vậy việc cộng và trừ nó sẽ không thay đổi mô-đun. Lưu ý rằng nó có kiểu không dấu và kết quả sẽ là số nguyên. Thật không may, nó không thể chuyển đổi chính xác các giá trị INT_MIN ... (- offset-1) vì chúng gây ra tràn arifmetic. Nhưng phương pháp này chỉ có một số học bổ sung duy nhất cho mỗi phép toán (và không có điều kiện) khi làm việc với bộ chia không đổi, vì vậy nó có thể sử dụng được trong các ứng dụng giống như DSP.

Có trường hợp đặc biệt, trong đó bộ chia là 2 N (lũy thừa nguyên của hai), mà modulo có thể được tính bằng cách sử dụng logic số học và bitwise đơn giản như

dividend&(divider-1)

ví dụ

x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15

Cách phổ biến hơn và ít phức tạp hơn là lấy modulo bằng hàm này (chỉ hoạt động với bộ chia dương):

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

Kết quả này chỉ chính xác nếu nó là âm tính.

Ngoài ra, bạn có thể lừa:

(p% q + q)% q

Nó rất ngắn nhưng sử dụng hai% -s thường chậm.


2

Tôi tin rằng một giải pháp khác cho vấn đề này sẽ là sử dụng các biến kiểu long thay vì int.

Tôi chỉ đang làm việc trên một số mã trong đó toán tử% trả về giá trị âm, điều này gây ra một số vấn đề (để tạo các biến ngẫu nhiên đồng nhất trên [0,1], bạn không thực sự muốn số âm :)), nhưng sau khi chuyển các biến thành gõ dài, mọi thứ đều chạy trơn tru và kết quả khớp với kết quả mà tôi nhận được khi chạy cùng một mã trong python (điều quan trọng đối với tôi là tôi muốn có thể tạo cùng một số "ngẫu nhiên" trên một số nền tảng.


2

Đây là câu trả lời mới cho một câu hỏi cũ, dựa trên bài báo Nghiên cứu của Microsoft này và các tài liệu tham khảo trong đó.

Lưu ý rằng từ C11 và C ++ 11 trở đi, ngữ nghĩa của divđã bị cắt bớt về phía không (xem [expr.mul]/4). Hơn nữa, đối với phép Dchia cho d, C ++ 11 đảm bảo những điều sau về thương qTvà phần dưrT

auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D));

trong đó signumánh xạ tới -1, 0, +1, tùy thuộc vào việc đối số của nó có <, ==,> hơn 0 hay không (xem phần Hỏi & Đáp này để biết mã nguồn).

Với phép chia cắt ngắn, dấu của phần dư bằng dấu của số bị chiaD , tức là -1 % 8 == -1. C ++ 11 cũng cung cấp một std::divhàm trả về một cấu trúc với các thành viên quotrem theo phép chia cắt ngắn.

Có thể có các định nghĩa khác, ví dụ: cái gọi là phép phân chia có cấu trúc phân lớp có thể được định nghĩa theo cách phân chia được cắt bớt nội dung

auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));

Với phép chia có lớp, dấu của phần dư bằng dấu của số chiad . Trong các ngôn ngữ như Haskell và Oberon, có các toán tử nội trang cho phép phân chia tầng. Trong C ++, bạn cần viết một hàm bằng các định nghĩa trên.

Tuy nhiên, một cách khác là phép phân chia Euclide , cũng có thể được định nghĩa theo cách phân chia cắt ngắn nội tại

auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) != -1);

Với phép chia Euclide, dấu của phần dư luôn là số dương .


2

Đối với giải pháp không sử dụng nhánh và chỉ 1 mod, bạn có thể làm như sau

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}

1
/ * Cảnh báo: macro mod đánh giá tác dụng phụ của đối số nhiều lần. * /
#define mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0)

... hoặc chỉ quen với việc lấy bất kỳ đại diện nào cho lớp tương đương.


2
"Làm quen với việc nhận bất kỳ đại diện nào cho lớp tương đương" ?! Đó là điều vô nghĩa. Nếu bạn muốn, bạn chỉ có thể sử dụng "đại diện" ban đầu r. Các %nhà điều hành không có gì để làm với các lớp tương đương. Đó là toán tử phần dư và phần dư được xác định rõ về mặt đại số để không âm và nhỏ hơn số chia. Đáng buồn thay, C đã định nghĩa nó một cách sai lầm. Tuy nhiên, hãy +1 vì có một trong những câu trả lời hay nhất.
R .. GitHub NGỪNG TRỢ GIÚP ICE

0

Mẫu ví dụ cho C ++

template< class T >
T mod( T a, T b )
{
    T const r = a%b;
    return ((r!=0)&&((r^b)<0) ? r + b : r);
}

Với mẫu này, phần còn lại được trả về sẽ bằng 0 hoặc có cùng dấu với số chia (mẫu số) (tương đương với việc làm tròn về phía âm vô cùng), thay vì hành vi C ++ của phần còn lại là 0 hoặc có cùng dấu với số bị chia ( tử số) (tương đương với làm tròn về 0).



-1
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}

-1

Giải pháp này (để sử dụng khi modlà số dương) tránh thực hiện các phép toán chia hoặc dư âm cùng nhau:

int core_modulus(int val, int mod)
{
    if(val>=0)
        return val % mod;
    else
        return val + mod * ((mod - val - 1)/mod);
}

-2

Tôi sẽ làm:

((-1)+8) % 8 

Điều này thêm số thứ hai vào số đầu tiên trước khi thực hiện modulo cho 7 như mong muốn. Điều này sẽ hoạt động đối với bất kỳ số nào xuống -8. Đối với -9, thêm 2 * 8.


2
Và đối với một biến có giá trị có thể là -99999?
Keith Thompson
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.