Cara membuat kode operator modulo (%) di C / C ++ / Obj-C yang menangani angka negatif

87

Salah satu hewan peliharaan saya yang membenci bahasa turunan C (sebagai ahli matematika) adalah itu

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

fmodf(-1,8) // fails similarly

Apa solusi terbaiknya?

C ++ memungkinkan kemungkinan templat dan operator kelebihan beban, tetapi keduanya adalah air keruh bagi saya. contoh diterima dengan rasa syukur.

P i
sumber
1
Saya tidak berpikir ini adalah "duplikat" dari stackoverflow.com/questions/828092/… di bawah definisi resmi. Tidak benar jawaban pertanyaan ini bisa digabung menjadi pertanyaan itu, karena pertanyaan ini hanya menanyakan tentang modulus, bukan pembagian. Tapi saya pikir pertanyaan ini tercakup dalam pertanyaan itu, jadi hampir saja. Jawaban saya sudah ada, FWIW.
Steve Jessop
Mungkin utas itu harus dipisahkan, karena menanyakan dua pertanyaan terpisah. cara terbaik untuk melakukannya adalah dengan menanyakan kembali pertanyaan pembagian secara terpisah dan kemudian mengarahkannya ke jawaban tersebut. Saya akan menyerahkannya kepada seseorang yang memahami mekanisme situs web ini dengan lebih baik.
P i
3
@Pi owhere %dikatakan sebagai modulo ... itu sisanya .
obataku
1
Berikut utas lain yang merupakan "duplikat" dari: stackoverflow.com/questions/1082917/… Hanya untuk referensi tentang %masalah ini .
leetNightshade
Jika Anda hanya membagi pangkat dua maka mungkin ide yang lebih baik untuk menggunakan dan:(-1) & 8 == 7
Henricus V.

Jawaban:

75

Pertama-tama saya ingin mencatat bahwa Anda bahkan tidak dapat mengandalkan fakta itu (-1) % 8 == -1. satu-satunya hal yang dapat Anda andalkan adalah itu (x / y) * y + ( x % y) == x. Namun apakah sisanya negatif atau tidak adalah definisi implementasi .

Sekarang mengapa menggunakan template di sini? Overload untuk int dan long akan dilakukan.

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

dan sekarang Anda dapat menyebutnya seperti mod (-1,8) dan akan tampak seperti 7.

Edit: Saya menemukan bug dalam kode saya. Ini tidak akan berhasil jika b negatif. Jadi saya pikir ini lebih baik:

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;
}

Referensi: C ++ 03 paragraf 5.6 klausul 4:

Biner / operator menghasilkan hasil bagi, dan operator% biner menghasilkan sisa dari pembagian ekspresi pertama dengan yang kedua. Jika operan kedua dari / atau% adalah nol, perilaku tidak terdefinisi; jika tidak (a / b) * b + a% b sama dengan a. Jika kedua operan nonnegatif maka sisanya nonnegatif; jika tidak, tanda sisanya ditentukan oleh implementasi .

Armen Tsirunyan
sumber
2
@Ohmu: Ya, itu dalam standar C ++. <quote> Untuk operan integral, operator / menghasilkan hasil bagi aljabar dengan bagian pecahan apa pun yang dibuang; jika hasil bagi a / b dapat direpresentasikan dalam jenis hasil, (a / b) * b + a% b sama dengan a. </quote>
Ben Voigt
5
-1. Sudah 11 tahun sejak implementasi ini ditetapkan. ISO 9899: 1999 mendefinisikannya, dan sayangnya memilih definisi yang buruk.
R .. GitHub STOP HELPING ICE
3
@Armen: Anda dengan mudah menghapus catatan kaki <quote> ... pembagian integer mengikuti aturan yang ditentukan dalam standar ISO Fortran, ISO / IEC 1539: 1991, di mana hasil bagi selalu dibulatkan ke nol </quote>. Standar C ++ baru meningkatkan perilaku ini dari "disukai" menjadi wajib, seperti Fortran dan C.
Ben Voigt
2
@Armen: Spesifikasi lama rusak, tetapi kerusakannya berbeda dari masalah tanda, dan mudah terlewat sampai Anda melihat kata-kata baru. C ++ 03 tidak memiliki "jika hasil bagi a / b dapat direpresentasikan dalam jenis hasil", yang menyebabkan masalah bagi INT_MIN / -1(pada implementasi komplemen dua). Di bawah spesifikasi lama, -32768 % -1mungkin harus mengevaluasi ke -65536(yang juga tidak dalam kisaran tipe 16-bit, yuck!) Agar identitasnya dipegang.
Ben Voigt
1
re "Namun apakah sisanya negatif atau tidak adalah implementasi-didefinisikan.", C ++ 11 menjamin bahwa pembagian integer membulatkan ke arah 0.
Cheers dan hth. - Alf
13

Berikut adalah fungsi C yang menangani bilangan bulat positif ATAU negatif ATAU nilai pecahan untuk KEDUA OPERAND

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

Ini pasti solusi paling elegan dari sudut pandang matematika. Namun, saya tidak yakin apakah itu kuat dalam menangani bilangan bulat. Terkadang kesalahan floating point merayap saat mengonversi int -> fp -> int.

Saya menggunakan kode ini untuk non-int s, dan fungsi terpisah untuk int.

CATATAN: perlu menjebak N = 0!

Kode penguji:

#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;
}

(Catatan: Anda dapat mengkompilasi dan menjalankannya langsung dari CodePad: http://codepad.org/UOgEqAMA )

Keluaran:

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

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

P i
sumber
Sayangnya, ini tidak berfungsi dengan bilangan bulat. Mereka perlu diubah menjadi floating point sebelum pembagian untuk memungkinkan Anda menggunakannya floor(). Selain itu, Anda mungkin kehilangan ketepatan saat mengonversi ke float: Coba (float)1000000001/3, Anda akan terkejut dengan hasilnya!
cmaster
9

Saya baru saja memperhatikan bahwa label Bjarne Stroustrup %sebagai operator sisa , bukan operator modulo.

Saya berani bertaruh bahwa ini adalah nama resminya dalam spesifikasi ANSI C & C ++, dan penyalahgunaan terminologi telah merayap masuk. Adakah yang mengetahui fakta ini?

Tetapi jika ini masalahnya maka fungsi fmodf () C (dan mungkin yang lain) sangat menyesatkan. mereka harus diberi label fremf (), dll

P i
sumber
1
Standar C11 (atau draf publik akhir tepatnya) menyebutkan "modulo" enam kali, tetapi hanya dalam kaitannya dengan representasi berbagai jenis. Tidak sekali melakukannya lagi "Modulo" dalam kaitannya dengan sisa operator ( %).
Nisse Engström
7

Fungsi umum paling sederhana untuk mencari modulo positif adalah sebagai berikut- Ini akan bekerja pada nilai x positif dan negatif.

int modulo(int x,int N){
    return (x % N + N) %N;
}
Udayraj Deshmukh
sumber
6

Untuk bilangan bulat, ini sederhana. Kerjakan saja

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

di mana saya mengandaikan itu Npositif dan terwakili dalam jenis x. Kompiler favorit Anda harus bisa mengoptimalkannya, sehingga hanya berakhir dalam satu operasi mod di assembler.

Jens Gustedt
sumber
3
Tidak bekerja: untuk int x=-9001; unsigned int N=2000;itu memberikan 2295, bukan 999.
Hubert Kario
1
@HubertKario Mungkin periksa lagi? Tidak mungkin modulo 2000 memberikan 2295, Anda pasti membuat kesalahan.
sam hocevar
2
@ SamHocevar: Saya pikir masalahnya di sini adalah aturan promosi integer C yang aneh. signed promosikan ke unsigned dan mempromosikan nilai integer bertanda negatif ke unsigned memicu perilaku tidak terdefinisi di C.
datenwolf
1
Saya percaya bentuk yang lebih sederhana (dan lebih efisien) akan menjadi: (x < 0) ? (x % N + N) : (x % N).
Chris Nolet
3

Solusi terbaik ¹untuk ahli matematika adalah menggunakan Python.

C ++ operator overloading tidak ada hubungannya dengan itu. Anda tidak dapat membebani operator untuk tipe bawaan. Yang Anda inginkan hanyalah sebuah fungsi. Tentu saja Anda dapat menggunakan template C ++ untuk mengimplementasikan fungsi itu untuk semua jenis yang relevan hanya dengan 1 bagian kode.

Pustaka C standar menyediakan fmod, jika saya mengingat namanya dengan benar, untuk tipe titik mengambang.

Untuk integer, Anda dapat menentukan template fungsi C ++ yang selalu mengembalikan sisa non-negatif (sesuai dengan divisi Euclidian) sebagai ...

#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);
}

... dan tulis saja, mod(a, b)bukan a%b.

Di sini tipe Integerharus tipe integer yang ditandatangani.

Jika Anda menginginkan perilaku matematika umum di mana tanda sisa sama dengan tanda pembagi, maka Anda dapat melakukannya misalnya

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 ); }

… Dengan batasan yang sama Integer, bahwa itu adalah tipe yang ditandatangani.


¹ Karena pembagian integer Python membulatkan ke arah negatif tak hingga.

Cheers and hth. - Alf
sumber
kode Anda tampaknya memiliki bug yang sama seperti milik saya sebelum saya edit. Bagaimana jika b negatif? :)
Armen Tsirunyan
1
@Armen: terima kasih! tapi saya terlalu malas untuk mengedit hanya untuk itu ... :-)
Cheers and hth. - Alf
@ArmenTsirunyan: rhasil harus make a= r + b*(a/b)true. tidak peduli bagaimana pembagian integer diimplementasikan b*somethingadalah kelipatan b. ini membuat rhasil modulo valid meskipun negatif. Anda dapat menambahkan abs ( b) ke dalamnya dan itu akan tetap menjadi hasil modulo yang valid.
Cheers and hth. - Alf
2
@downvoters: Jawaban ini masih benar, sedangkan "solusi" yang dipilih sekarang berisi komentar yang salah karena jaminan baru di C ++ 11. Sungguh ironis untuk tidak menyukai jawaban yang masih benar. Tanpa alasan yang diberikan, seseorang harus berasumsi bahwa setidaknya 2 orang asosiatif, dengan tingkat ketidaktahuan yang hampir absolut, membaca komentar pertanyaan ini dan secara asosiatif tidak setuju. Tolong jelaskan downvote Anda.
Cheers and hth. - Alf
1
Hasil yang diinginkan secara matematis adalah sisa menjadi nol atau memiliki tanda yang sama dengan penyebut (penyebut). Jika pembaginya negatif, maka sisanya harus nol atau negatif. Hasil implementasi C / C ++ di sisa menjadi nol atau bertanda sama dengan pembilang (pembilang).
rcgldr
2

Oh, aku juga benci% mendesain untuk ini ....

Anda dapat mengubah dividen menjadi unsigned dengan cara seperti:

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

result = (offset + dividend) % divider

di mana offset paling dekat dengan (-INT_MIN) kelipatan modul, jadi menambahkan dan menguranginya tidak akan mengubah modulo. Perhatikan bahwa itu memiliki tipe unsigned dan hasilnya akan berupa integer. Sayangnya itu tidak dapat dengan benar mengubah nilai INT_MIN ... (- offset-1) karena menyebabkan arifmetic overflow. Tetapi metode ini memiliki keunggulan hanya aritmatika tambahan tunggal per operasi (dan tidak ada persyaratan) saat bekerja dengan pembagi konstan, sehingga dapat digunakan dalam aplikasi seperti DSP.

Ada kasus khusus, di mana pembagi adalah 2 N (bilangan bulat pangkat dua), di mana modulo dapat dihitung menggunakan aritmatika sederhana dan logika bitwise sebagai

dividend&(divider-1)

sebagai contoh

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

Cara yang lebih umum dan tidak terlalu rumit adalah dengan mendapatkan modulo menggunakan fungsi ini (hanya berfungsi dengan pembagi positif):

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

Ini hanya hasil yang benar jika negatif.

Anda juga bisa menipu:

(p% q + q)% q

Ini sangat singkat tetapi gunakan dua% -s yang biasanya lambat.

Vovanium
sumber
2

Saya percaya solusi lain untuk masalah ini akan digunakan untuk variabel tipe long, bukan int.

Saya baru saja mengerjakan beberapa kode di mana% operator mengembalikan nilai negatif yang menyebabkan beberapa masalah (untuk menghasilkan variabel acak seragam pada [0,1] Anda tidak benar-benar menginginkan angka negatif :)), tetapi setelah mengalihkan variabel ke ketik panjang, semuanya berjalan lancar dan hasilnya cocok dengan yang saya dapatkan saat menjalankan kode yang sama dengan python (penting bagi saya karena saya ingin dapat menghasilkan angka "acak" yang sama di beberapa platform.

David
sumber
2

Berikut adalah jawaban baru untuk pertanyaan lama, berdasarkan makalah Riset Microsoft ini dan referensi di dalamnya.

Perhatikan bahwa dari C11 dan C ++ 11 dan seterusnya, semantik divtelah dipotong ke arah nol (lihat [expr.mul]/4). Selanjutnya, untuk Ddibagi dengan d, C ++ 11 menjamin hal-hal berikut tentang hasil bagi qTdan sisarT

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));

di mana signumdipetakan ke -1, 0, +1, tergantung pada apakah argumennya <, ==,> dari 0 (lihat Tanya Jawab ini untuk kode sumber).

Dengan pembagian terpotong, tanda sisa sama dengan tanda pembagianD , yaitu -1 % 8 == -1. C ++ 11 juga menyediakan std::divfungsi yang mengembalikan struct dengan anggota quotdan remmenurut pembagian terpotong.

Ada definisi lain yang mungkin, misalnya yang disebut divisi berlantai dapat didefinisikan dalam istilah divisi terpotong bawaan

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));

Dengan pembagian berlantai, tanda sisa sama dengan tanda pembagid . Dalam bahasa seperti Haskell dan Oberon, terdapat operator builtin untuk divisi lantai. Di C ++, Anda perlu menulis fungsi menggunakan definisi di atas.

Namun cara lain adalah divisi Euclidean , yang juga dapat didefinisikan dalam istilah divisi terpotong bawaan

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);

Dengan pembagian Euclidean, tanda sisanya selalu positif .

TemplateRex
sumber
2

Untuk solusi yang tidak menggunakan cabang dan hanya 1 mod, Anda dapat melakukan hal berikut

// 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);
}
Kyle Butt
sumber
1
/ * Peringatan: mod makro mengevaluasi efek samping argumennya beberapa kali. * /
# Tentukan mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0)

... atau biasakan mendapatkan perwakilan untuk kelas kesetaraan.

Eric Towers
sumber
2
"Biasakan mendapatkan perwakilan untuk kelas kesetaraan" ?! Itu tidak masuk akal. Jika Anda ingin, Anda bisa menggunakan "perwakilan" asli r. The %Operator tidak ada hubungannya dengan kelas kesetaraan. Ini adalah operator sisa dan sisanya didefinisikan dengan baik secara aljabar menjadi nonnegatif dan kurang dari pembagi. Sayangnya C mendefinisikannya dengan cara yang salah. Tetap saja, +1 karena memiliki salah satu jawaban terbaik.
R .. GitHub STOP HELPING ICE
0

Contoh template untuk C ++

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

Dengan templat ini, sisa yang dikembalikan akan menjadi nol atau memiliki tanda yang sama dengan penyebut (penyebut) (ekuivalen dengan pembulatan menuju tak terhingga negatif), alih-alih perilaku C ++ dari sisanya menjadi nol atau memiliki tanda yang sama dengan pembagi ( pembilang) (setara dengan pembulatan menuju nol).

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

Solusi ini (untuk digunakan ketika modpositif) menghindari pembagian negatif atau operasi sisa semuanya bersama-sama:

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

Saya akan melakukan:

((-1)+8) % 8 

Ini menambahkan angka terakhir ke yang pertama sebelum melakukan modulo memberikan 7 seperti yang diinginkan. Ini harus bekerja untuk nomor apa pun hingga -8. Untuk -9 tambahkan 2 * 8.

Toby O'Connell
sumber
2
Dan untuk variabel yang nilainya mungkin -99999?
Keith Thompson