JavaScript% (modulo) memberikan hasil negatif untuk angka negatif

253

Menurut Google Kalkulator (-13) % 64 adalah 51.

Menurut Javascript (lihat JSBin ini ) itu -13.

Bagaimana cara saya memperbaikinya?

Alec Gorge
sumber
Ini mungkin hanya masalah prioritas. Apakah maksud Anda (-13) % 64atau -(13 % 64)? Secara pribadi, saya akan memasukkan parens baik, hanya untuk kejelasan ekstra.
MatrixFrog
2
dasarnya duplikat dari Bagaimana java melakukan perhitungan modulus dengan angka negatif? meskipun ini adalah pertanyaan javascript.
Presiden James K. Polk
85
Javascript terkadang terasa seperti lelucon yang sangat kejam
dukeofgaming
6
Google tidak dapat salah
caub
10
Masalah mendasar adalah di JS %bukan operator modulo. Ini operator sisanya. Tidak ada operator modulo di JavaScript. Jadi jawaban yang diterima adalah jalan yang harus ditempuh.
Redu

Jawaban:

263
Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

Diambil dari artikel ini: Bug JavaScript Modulo

Enrique
sumber
23
Saya tidak tahu bahwa saya akan menyebutnya "bug". Operasi modulo tidak didefinisikan dengan baik pada angka negatif, dan lingkungan komputasi yang berbeda menanganinya secara berbeda. Artikel Wikipedia tentang operasi modulo membahasnya dengan cukup baik.
Daniel Pryden
22
Ini mungkin tampak bodoh karena sering disebut 'modulo', menunjukkan bahwa ia akan berperilaku sama dengan definisi matematika (lihat aljabar ℤ / nℤ), yang tidak.
etienne
7
Mengapa mengambil modulo sebelum menambahkan n? Mengapa tidak menambahkan n lalu mengambil modulo?
dibintangi
12
@ starwed jika Anda tidak menggunakan ini% n akan gagal untuk x < -n- misalnya (-7 + 5) % 5 === -2tetapi ((-7 % 5) + 5) % 5 == 3.
fadedbee
7
Saya sarankan untuk menambahkan jawaban bahwa untuk mengakses fungsi ini kita harus menggunakan format (-13) .mod (10), bukan -13% 10. Akan lebih jelas.
Jp_
161

Menggunakan Number.prototypeSLOW, karena setiap kali Anda menggunakan metode prototipe nomor Anda dibungkus dengan Object. Alih-alih ini:

Number.prototype.mod = function(n) {
  return ((this % n) + n) % n;
}

Menggunakan:

function mod(n, m) {
  return ((n % m) + m) % m;
}

Lihat: http://jsperf.com/negative-modulo/2

~ 97% lebih cepat daripada menggunakan prototipe. Jika kinerja sangat penting bagi Anda tentu saja ..

StuR
sumber
1
Tip yang bagus. Saya mengambil jsperf Anda dan membandingkannya dengan sisa solusi dalam pertanyaan ini (tapi sepertinya ini yang terbaik): jsperf.com/negative-modulo/3
Mariano Desanze
11
Optimalisasi mikro. Anda harus melakukan sejumlah besar perhitungan mod untuk ini untuk membuat perbedaan apa pun. Kode apa yang paling jelas dan paling dapat dipertahankan, kemudian optimalkan analisis kinerja berikut.
ChrisV
Saya pikir Anda punya Anda ndan ms sekitar dengan cara yang salah dalam contoh @StuR kedua Anda. Seharusnya begitu return ((n % m) + m) % m;.
vimist
Ini harus menjadi komentar untuk jawaban yang diterima, bukan jawaban untuk dirinya sendiri.
xehpuk
5
Motivasi yang dinyatakan dalam jawaban ini adalah mikro-optimasi, ya, tetapi memodifikasi prototipe bermasalah. Lebih suka pendekatan dengan efek samping paling sedikit, yang ini.
Tertarik
31

The %operator dalam JavaScript adalah operator sisa, bukan operator modulo (perbedaan utama adalah bagaimana angka negatif diperlakukan):

-1 % 8 // -1, not 7

Rob Sobers
sumber
8
Ini harus disebut operator sisanya tetapi adalah menelepon operator modulus: developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/...
Big McLargeHuge
16
@DaveKennedy: MDN bukan referensi bahasa resmi, ini adalah situs yang diedit oleh komunitas yang terkadang salah. Spec tidak menyebutnya operator modulo, dan sejauh yang saya tahu tidak pernah (saya kembali ke ES3). Secara eksplisit mengatakan operator menghasilkan sisa dari divisi tersirat, dan hanya menyebutnya "operator%."
TJ Crowder
2
Jika itu disebut remainder, itu harus lebih besar dari 0 menurut definisi. Tidak bisakah kau ingat teorema pembagian dari sekolah tinggi ?! Jadi mungkin Anda bisa melihatnya di sini: en.wikipedia.org/wiki/Euclidean_division
Ahmad
19

Fungsi "mod" untuk mengembalikan hasil positif.

var mod = function (n, m) {
    var remain = n % m;
    return Math.floor(remain >= 0 ? remain : remain + m);
};
mod(5,22)   // 5
mod(25,22)  // 3
mod(-1,22)  // 21
mod(-2,22)  // 20
mod(0,22)   // 0
mod(-1,22)  // 21
mod(-21,22) // 1

Dan tentu saja

mod(-13,64) // 51
Shanimal
sumber
1
MDN bukan referensi bahasa resmi, ini adalah situs yang diedit oleh komunitas yang terkadang salah. Spek tidak menyebutnya operator modulo, dan sejauh yang saya tahu tidak pernah (saya kembali ke ES3). Secara eksplisit mengatakan operator menghasilkan sisa dari divisi tersirat, dan hanya menyebutnya "operator%."
TJ Crowder
1
Ups, tautan yang Anda tentukan sebenarnya adalah referensi #sec-applying-the-mod-operatordi url :) Pokoknya, terima kasih atas catatannya, saya mengambil jawaban saya, itu tidak terlalu penting.
Shanimal
3
@ Shanimal: LOL! Itu benar. Kesalahan oleh editor HTML. Teks spesifikasi tidak.
TJ Crowder
10

Jawaban yang diterima membuat saya sedikit gugup karena menggunakan kembali% operator. Bagaimana jika Javascript mengubah perilaku di masa depan?

Berikut ini solusinya yang tidak menggunakan kembali%:

function mod(a, n) {
    return a - (n * Math.floor(a/n));
}

mod(1,64); // 1
mod(63,64); // 63
mod(64,64); // 0
mod(65,64); // 1
mod(0,64); // 0
mod(-1,64); // 63
mod(-13,64); // 51
mod(-63,64); // 1
mod(-64,64); // 0
mod(-65,64); // 63
wisbucky
sumber
8
Jika javascript mengubah operator modulo agar sesuai dengan definisi matematika, jawaban yang diterima akan tetap berfungsi.
starwed
20
"Bagaimana jika Javascript mengubah perilaku di masa depan?" - Kenapa begitu? Mengubah perilaku operator fundamental semacam itu tidak mungkin.
nnnnnn
1
+1 untuk membagikan keprihatinan-tentang & alternatif-ini ke jawaban unggulan # answer-4467559 & untuk 4 alasan: (1) Mengapa ini menyatakan, & ya "Mengubah perilaku operasi fundamental semacam itu tidak mungkin" tetapi masih bijaksana untuk mempertimbangkan bahkan untuk menemukannya tidak diperlukan. (2) mendefinisikan op yang berfungsi dalam hal yang rusak, meskipun mengesankan, mengkhawatirkan setidaknya pada tampilan pertama, pada harus sampai tidak ditunjukkan (3) jika saya belum memverifikasi alternatif ini dengan baik, saya menemukan lebih mudah untuk mengikuti lihat cepat. (4) mungil: ia menggunakan 1 div + 1 mul, bukannya 2 div (mod) & saya pernah mendengar di banyak perangkat keras sebelumnya tanpa FPU yang baik, perkaliannya lebih cepat.
Destiny Architect
2
@DestinyArchitect itu tidak bijaksana, tidak ada gunanya. Jika mereka mengubah perilaku operator yang tersisa, itu akan merusak berbagai program yang menggunakannya. Itu tidak akan pernah terjadi.
Aegis
10
Bagaimana jika perilaku -, *, /, ;, ., (, ), ,, Math.floor, functionatau returnperubahan? Kemudian kode Anda rusak parah.
xehpuk
5

Meskipun tidak berperilaku seperti yang Anda harapkan, itu tidak berarti bahwa JavaScript tidak 'berperilaku'. Ini adalah pilihan JavaScript yang dibuat untuk perhitungan modulo-nya. Karena, menurut definisi, kedua jawaban itu masuk akal.

Lihat ini dari Wikipedia. Anda dapat melihat di sebelah kanan bagaimana berbagai bahasa memilih tanda hasil.

dheerosaur
sumber
4

Jika xbilangan bulat dan nmerupakan kekuatan 2, Anda dapat menggunakan x & (n - 1)sebagai gantinya x % n.

> -13 & (64 - 1)
51 
quasimodo
sumber
2

Jadi sepertinya jika Anda mencoba mod sekitar derajat (sehingga jika Anda memiliki -50 derajat - 200 derajat), Anda ingin menggunakan sesuatu seperti:

function modrad(m) {
    return ((((180+m) % 360) + 360) % 360)-180;
}
JayCrossler
sumber
1

Saya berurusan dengan negatif dan negatif juga

 //best perf, hard to read
   function modul3(a,n){
        r = a/n | 0 ;
        if(a < 0){ 
            r += n < 0 ? 1 : -1
        }
        return a - n * r 
    }
    // shorter code
    function modul(a,n){
        return  a%n + (a < 0 && Math.abs(n)); 
    }

    //beetween perf and small code
    function modul(a,n){
        return a - n * Math[n > 0 ? 'floor' : 'ceil'](a/n); 
    }
bormat
sumber
1

Ini bukan bug, ada 3 fungsi untuk menghitung modulo, Anda dapat menggunakan yang sesuai dengan kebutuhan Anda (saya akan merekomendasikan untuk menggunakan fungsi Euclidean)

Memotong fungsi bagian desimal

console.log(  41 %  7 ); //  6
console.log( -41 %  7 ); // -6
console.log( -41 % -7 ); // -6
console.log(  41 % -7 ); //  6

Fungsi bagian integer

Number.prototype.mod = function(n) {
    return ((this%n)+n)%n;
};

console.log( parseInt( 41).mod( 7) ); //  6
console.log( parseInt(-41).mod( 7) ); //  1
console.log( parseInt(-41).mod(-7) ); // -6
console.log( parseInt( 41).mod(-7) ); // -1

Fungsi Euclidean

Number.prototype.mod = function(n) {
    var m = ((this%n)+n)%n;
    return m < 0 ? m + Math.abs(n) : m;
};

console.log( parseInt( 41).mod( 7) ); // 6
console.log( parseInt(-41).mod( 7) ); // 1
console.log( parseInt(-41).mod(-7) ); // 1
console.log( parseInt( 41).mod(-7) ); // 6
zessx
sumber
1
Dalam fungsi euclidian, pengecekan m <0 tidak berguna karena ((% n ini) + n)% n selalu positif
bormat
1
@ keset Ya, tetapi dalam Javascript %dapat mengembalikan hasil negatif (dan ini adalah tujuan dari fungsi-fungsi ini, untuk memperbaikinya)
zessx
Anda menulis [kode] ini Number.prototype.mod = fungsi (n) {var m = ((ini% n) + n)% n; kembali m <0? m + Math.abs (n): m; }; [/ kode] beri saya satu nilai n di mana m adalah négative. tidak ada nilai n di mana m adalah négative karena Anda menambahkan n setelah% pertama.
bormat
Tanpa pemeriksaan ini, parseInt(-41).mod(-7)akan kembali -6sebagai gantinya 1(dan inilah tepatnya tujuan fungsi bagian Integer yang saya tulis)
zessx
1
Anda dapat menyederhanakan fungsi Anda dengan menghapus modulo Number.prototype.mod = fungsi kedua (n) {var m = this% n; kembali (m <0)? m + Math.abs (n): m; };
bormat
0

Ada paket NPM yang akan melakukan pekerjaan untuk Anda. Anda dapat menginstalnya dengan perintah berikut.

npm install just-modulo --save

Penggunaan disalin dari README

import modulo from 'just-modulo';

modulo(7, 5); // 2
modulo(17, 23); // 17
modulo(16.2, 3.8); // 17
modulo(5.8, 3.4); //2.4
modulo(4, 0); // 4
modulo(-7, 5); // 3
modulo(-2, 15); // 13
modulo(-5.8, 3.4); // 1
modulo(12, -1); // NaN
modulo(-3, -8); // NaN
modulo(12, 'apple'); // NaN
modulo('bee', 9); // NaN
modulo(null, undefined); // NaN

Repositori GitHub dapat ditemukan melalui tautan berikut:

https://github.com/angus-c/just/tree/master/packages/number-modulo

maartenpaauw
sumber