Divisi integer dengan sisa dalam JavaScript?


Jawaban:


1239

Untuk beberapa nomor ydan beberapa pembagi xmenghitung hasil bagi ( quotient) dan sisanya ( remainder) sebagai:

var quotient = Math.floor(y/x);
var remainder = y % x;

84
% berfungsi pada mengapung di JavaScript (ini berbeda dari banyak bahasa lain), yang mungkin tidak diinginkan: 3.5 % 2mengevaluasi menjadi 1,5. Pastikan untuk menangani (parseInt, floor, dll.) Seperti yang diperlukan

16
Bagian integral -4,5 dalam matematika adalah -5, karena -5 adalah "angka integral tertinggi yang mungkin masih lebih rendah dari -4,5".
Toughy

9
Namun, apa pun yang Anda putuskan untuk lakukan dengan angka negatif, itu harus konsisten antara hasil bagi dan sisanya. Menggunakan floordan %bersama - sama tidak konsisten dengan cara itu. Baik gunakan truncalih-alih floor(dengan demikian mengizinkan sisa negatif) atau gunakan pengurangan untuk mendapatkan sisanya ( rem = y - div * x).
Mark Reed

7
1. Jika Anda akan menghitung sisanya rempula, Anda bisa mendapatkan hasil bagi divlebih cepat tanpa lantai: (y - rem) / x. 2. Ngomong-ngomong operasi modulo oleh definisi yang direkomendasikan Donald Knuth (tanda-cocok-divisor, bukan sisanya yaitu modulus Euclidean, atau JavaScript tanda-cocok-dividen) adalah apa yang bisa kita kode dalam JavaScript sebagai function mod (a, n) { return a % n + (Math.sign(a) !== Math.sign(n) ? n : 0); }.
Aaron Mansheim

1
-9 / 2 = -4.5. Anda kemudian mengambil lantai -4,5, yaitu -5. Ingatlah bahwa -5 kurang dari -4,5, dan operasi lantai didefinisikan sebagai bilangan bulat terbesar kurang dari nilai yang diberikan.
Mark Reed

371

Saya bukan ahli dalam operator bitwise, tapi inilah cara lain untuk mendapatkan seluruh nomor:

var num = ~~(a / b);

Ini akan bekerja dengan baik untuk angka negatif juga, sementara Math.floor()akan membulatkan arah yang salah.

Ini tampaknya benar juga:

var num = (a / b) >> 0;

84
Satu lagi, yang tujuannya saya hanya menghabiskan 20 menit terakhir untuk mencari tahu, tampaknyaa/b | 0
BlueRaja - Danny Pflughoeft

18
@ user113716 @BlueRaja Operasi bitwise hanya masuk akal pada tipe integer dan JS (tentu saja) tahu itu. ~~int, int | 0dan int >> 0tidak mengubah argumen awal, tetapi menjadikan interpreter pass sebagai bagian integral dari operator.
Aleksei Zabrodskii

15
floorhampir tidak berputar ke arah yang salah, diberi nama - hanya saja bukan arah yang umumnya orang inginkan!
Mark K Cowan

19
Itu buu buu. a = 12447132275286670000; b = 128 Math.floor(a/b)-> 97243220900677100dan ~~(a/b)-> -1231452688.
Mirek Rusin

7
Hati-hati dengan yang diutamakan. ~~(5/2) --> 2seperti halnya (5/2)>>0 --> 2, tapi ~~(5/2) + 1 --> 3, sementara ~~(5/2)>>0 + 1 --> 1. ~~adalah pilihan yang baik karena diutamakan lebih tepat.
timkay

217

Saya melakukan beberapa tes kecepatan di Firefox.

-100/3             // -33.33..., 0.3663 millisec
Math.floor(-100/3) // -34,       0.5016 millisec
~~(-100/3)         // -33,       0.3619 millisec
(-100/3>>0)        // -33,       0.3632 millisec
(-100/3|0)         // -33,       0.3856 millisec
(-100-(-100%3))/3  // -33,       0.3591 millisec

/* a=-100, b=3 */
a/b                // -33.33..., 0.4863 millisec
Math.floor(a/b)    // -34,       0.6019 millisec
~~(a/b)            // -33,       0.5148 millisec
(a/b>>0)           // -33,       0.5048 millisec
(a/b|0)            // -33,       0.5078 millisec
(a-(a%b))/b        // -33,       0.6649 millisec

Di atas didasarkan pada 10 juta percobaan untuk masing-masing.

Kesimpulan: Gunakan (a/b>>0)(atau (~~(a/b))atau (a/b|0)) untuk mencapai efisiensi sekitar 20%. Juga perlu diingat bahwa mereka semua tidak konsisten dengan Math.floor, kapan a/b<0 && a%b!=0.


54
Perhatikan bahwa mengoptimalkan pembagian integer untuk kecepatan akan masuk akal hanya jika Anda melakukannya banyak . Dalam kasus lain saya akan merekomendasikan memilih yang paling sederhana (mana yang tampaknya paling sederhana untuk Anda dan rekan kerja Anda).
mik01aj

7
@ m01 sepenuhnya setuju - ada terlalu banyak fokus pada hal-hal seperti ini online
JonnyRaa

2
@ m01 Tapi mana yang lebih sulit: belajar tentang Math.floordan siapa-tahu-berapa-banyak fungsi API lainnya, atau belajar tentang ~operator (bitwise-not) dan bagaimana operasi bitwise bekerja di JS dan kemudian memahami efek double tilde?
Stijn de Witt

11
Nah, jika rekan kerja Anda tidak memprogram chip dalam assembler, mereka mungkin akan Math.floorlebih mengerti . Dan kalaupun tidak, yang ini bisa di-googleable.
mik01aj

2
@ MarkGreen ya tapi hanya ingin melakukan itu tidak berarti Anda harus menulisnya dalam bentuk yang aneh hanya karena itu terjadi tercepat, tanpa alasan yang bagus - kejelasan kode pada umumnya biasanya menjadi perhatian pertama Anda. Juga tes ini mungkin sama sekali tidak berarti setelah perubahan bahasa dan di berbagai browser - Anda perlu profil untuk mengetahui apa yang lambat dalam aplikasi Anda. Ini tidak mungkin menjadi metode pembagian integer Anda kecuali Anda sudah mengoptimalkan satu hal lainnya!
JonnyRaa

150

ES6 memperkenalkan Math.truncmetode baru . Ini memungkinkan untuk memperbaiki jawaban @ MarkElliot untuk membuatnya berfungsi untuk angka negatif juga:

var div = Math.trunc(y/x);
var rem = y % x;

Perhatikan bahwa Mathmetode memiliki keunggulan dibandingkan operator bitwise yang mereka gunakan dengan angka di atas 2 31 .


var y = 18014398509481984; var x = 5; div =? - bug?
4esn0k

4
@ 4esn0k Ini bukan bug. Angka Anda memiliki terlalu banyak digit, Anda tidak dapat memiliki presisi sebanyak itu dalam format biner IEEE 754 bit 64-bit. Sebagai contoh 18014398509481984 == 18014398509481985,.
Oriol

18014398509481984 == 2 ** 54, dan saya secara khusus menggunakan angka ini, karena diwakili persis dalam format binary64. dan jawabannya diwakili persis juga
4esn0k

1
Saya pikir pilihannya sederhana: Anda memerlukan dukungan untuk nomor hingga 32 bit yang ditandatangani? Gunakan ~~(x/y). Perlu mendukung jumlah yang lebih besar hingga 54 bit yang ditandatangani? Gunakan Math.truncjika Anda memilikinya, atau Math.floorsebaliknya (koreksi untuk angka negatif). Perlu mendukung jumlah yang lebih besar lagi? Gunakan beberapa perpustakaan angka besar.
Stijn de Witt

4
untuk ruby ​​di sini dari google untuk mencari divmod, Anda dapat menerapkannya seperti:function divmod(x, y) { var div = Math.trunc(x/y); var rem = x % y; return [div, rem]; }
Alex Moore-Niemi

29
var remainder = x % y;
return (x - remainder) / y;

1
Sayangnya, versi ini gagal dalam pengujian saat x = -100 karena mengembalikan -34 bukan -33.
Samuel

1
bagaimana dengan "var x = 0,3; var y = 0,01;" ? (terima kasih kepada github.com/JuliaLang/julia/issues/4156#issuecomment-23324163 )
4esn0k

Sebenarnya, @Samuel, dengan nilai negatif, metode ini mengembalikan hasil yang benar, atau setidaknya mengembalikan nilai yang sama dengan metode menggunakan Math.trunc:). Saya memeriksa dengan 100,3; -100,3; 100, -3 dan -100, -3. Tentu saja, banyak waktu telah berlalu sejak komentar Anda dan banyak hal berubah.
Marjan Venema

19

Saya biasanya menggunakan:

const quotient =  (a - a % b) / b;
const remainder = a % b;

Ini mungkin bukan yang paling elegan, tetapi berhasil.


1
Solusi yang bagus karena menghindari kejelekan dari parsing atau memotong float.
Dem Pilafian

6
jika Anda membutuhkan hasil bagi dan sisanya, hitung sisanya terlebih dahulu kemudian gunakan kembali nilai itu dalam ekspresi untuk hasil bagi, yaitu hasil bagi = (a - sisa) / b;
gb96

2
sisanya = a% b; quotient = (a - sisa) / b;
Zv_oDD

16

Anda dapat menggunakan fungsi ini parseIntuntuk mendapatkan hasil yang terpotong.

parseInt(a/b)

Untuk mendapatkan sisanya, gunakan mod operator:

a%b

parseInt memiliki beberapa perangkap dengan string, untuk menghindari penggunaan parameter radix dengan basis 10

parseInt("09", 10)

Dalam beberapa kasus representasi string nomor dapat menjadi notasi ilmiah, dalam hal ini, parseInt akan menghasilkan hasil yang salah.

parseInt(100000000000000000000000000000000, 10) // 1e+32

Panggilan ini akan menghasilkan 1 sebagai hasilnya.


7
parseIntharus dihindari bila memungkinkan. Berikut ini peringatan Douglas Crockford: "Jika karakter pertama dari string adalah 0, maka string tersebut dievaluasi dalam basis 8, bukan basis 10. Dalam basis 8, 8 dan 9 bukan digit, jadi parseInt (" 08 ") dan parseInt ("09") menghasilkan 0 sebagai hasilnya. Kesalahan ini menyebabkan masalah dalam program yang menguraikan tanggal dan waktu. Untungnya, parseInt dapat mengambil parameter radix, sehingga parseInt ("08", 10) menghasilkan 8. Saya sarankan Anda selalu berikan parameter radix. " archive.oreilly.com/pub/a/javascript/excerpts/…
Powers

3
Dalam sebuah divisi, saya berharap menerima angka, bukan string, tetapi ini adalah poin yang bagus.
Édipo Costa Rebouças

2
@Power jadi tambahkan radix. Dia tidak mengatakan parseIntharus dihindari; Hanya saja ada beberapa gotcha yang harus diperhatikan. Anda harus menyadari hal-hal ini dan bersiap untuk mengatasinya.
Tidak ada

2
Jangan pernah menelepon parseIntdengan argumen nomor. parseIntseharusnya mengurai string sebagian-numerik, bukan angka terpotong.
Oriol

1
Hanya karena segala sesuatu pada awalnya tidak dimaksudkan untuk digunakan dengan cara tertentu, itu tidak berarti Anda tidak boleh melakukannya. Jawaban ini berhasil.
fregante

6

JavaScript menghitung dengan tepat lantai bilangan negatif dan sisa bilangan non-integer, mengikuti definisi matematika untuknya.

LANTAI didefinisikan sebagai "bilangan bulat terbesar yang lebih kecil dari parameter", dengan demikian:

  • angka positif: FLOOR (X) = bilangan bulat bagian X;
  • angka negatif: FLOOR (X) = bilangan bulat bagian X minus 1 (karena itu harus KECIL daripada parameter, yaitu, lebih negatif!)

REMAINDER didefinisikan sebagai "sisa" dari suatu divisi (Euclidean arithmetic). Ketika dividen bukan bilangan bulat, hasil bagi biasanya juga bukan bilangan bulat, yaitu, tidak ada sisa, tetapi jika hasil bagi dipaksa menjadi bilangan bulat (dan itulah yang terjadi ketika seseorang mencoba untuk mendapatkan sisa atau modulus dari suatu angka floating-point), akan ada "sisa" non-integer, jelas.

JavaScript memang menghitung semuanya seperti yang diharapkan, sehingga programmer harus berhati-hati untuk mengajukan pertanyaan yang tepat (dan orang-orang harus berhati-hati untuk menjawab apa yang ditanyakan!) Pertanyaan pertama Yarin bukanlah "apa pembagian bilangan X dengan Y", tapi, sebagai gantinya, "SELURUH berapa kali bilangan bulat yang diberikan PERGI ke yang lain". Untuk bilangan positif, jawabannya adalah sama untuk keduanya, tetapi tidak untuk bilangan negatif, karena pembagian bilangan bulat (dividen dengan pembagi) akan -1 lebih kecil daripada kali angka (pembagi) "masuk ke" yang lain (dividen). Dengan kata lain, FLOOR akan mengembalikan jawaban yang benar untuk pembagian bilangan bulat dari angka negatif, tetapi Yarin tidak menanyakan itu!

gammax menjawab dengan benar, kode itu berfungsi seperti yang diminta oleh Yarin. Di sisi lain, Samuel salah, dia tidak menghitung, kurasa, atau dia akan melihat bahwa itu berhasil (juga, dia tidak mengatakan apa yang menjadi pembagi dari teladannya, tetapi saya berharap itu adalah 3):

Sisa = X% Y = -100% 3 = -1

GoesInto = (X - Sisa) / Y = (-100 - -1) / 3 = -99 / 3 = -33

By the way, saya menguji kode pada Firefox 27.0.1, itu berfungsi seperti yang diharapkan, dengan angka positif dan negatif dan juga dengan nilai-nilai non-integer, baik untuk dividen dan pembagi. Contoh:

-100.34 / 3.57: GoesInto = -28, Sisa = -0.380000000000000079

Ya, saya perhatikan, ada masalah presisi di sana, tapi saya tidak punya waktu untuk memeriksanya (saya tidak tahu apakah itu masalah dengan Firefox, Windows 7 atau dengan FPU CPU saya). Namun untuk pertanyaan Yarin, yang hanya melibatkan bilangan bulat, kode gammax berfungsi dengan baik.


5

Math.floor(operation) mengembalikan nilai pembulatan operasi.

Contoh 1 st pertanyaan:

var x = 5;
var y = 10.4;
var z = Math.floor(x + y);

console.log(z);

Menghibur:

15

Contoh 2 nd pertanyaan:

var x = 14;
var y = 5;
var z = Math.floor(x%y);

console.log(x);

Menghibur:

4



1

Komentar Alex Moore-Niemi sebagai jawaban:

Untuk Rubyist di sini dari Google dalam pencarian divmod, Anda dapat mengimplementasikannya seperti:

function divmod(x, y) {
  var div = Math.trunc(x/y);
  var rem = x % y;
  return [div, rem];
}

Hasil:

// [2, 33]

2
Biasanya, divmodmenggunakan pembagian lantai ( Math.floor), yang berbeda dari divisi terpotong ( Math.trunc) ketika angka negatif terlibat. Ini adalah kasus untuk paket NPMdivmod , Rubydivmod , SWI-Prologdivmod dan mungkin banyak implementasi lainnya juga.
Palec

Divisi terpotong memberikan hasil yang lebih alami daripada divisi lantai, tetapi kompatibilitas mengalahkan itu, IMO. Mungkin, ada alasan matematika atau kinerja untuk menggunakan divisi berlantai juga. Perhatikan bahwa biasanya, divmodada karena ia melakukan dua kali lebih cepat dari komputasi dua operasi secara terpisah. Menyediakan fungsi seperti itu tanpa manfaat kinerja ini mungkin membingungkan.
Palec

1

Jika Anda hanya membagi dengan kekuatan dua, Anda dapat menggunakan operator bitwise:

export function divideBy2(num) {
  return [num >> 1, num & 1];
}

export function divideBy4(num) {
  return [num >> 2, num & 3];
}

export function divideBy8(num) {
  return [num >> 3, num & 7];
}

(Yang pertama adalah hasil bagi, yang kedua sisanya)


Secara umum function divideByPowerOf2(num, exponent) { return [num >> exponent, num & ((1 << exponent) - 1)]; },.
Palec

0

Anda dapat menggunakan ternary untuk memutuskan bagaimana menangani nilai integer positif dan negatif juga.

var myInt = (y > 0) ? Math.floor(y/x) : Math.floor(y/x) + 1

Jika angkanya positif, semuanya baik-baik saja. Jika angkanya negatif, itu akan menambah 1 karena cara Math.floor menangani negatif.


0

Ini akan selalu terpotong menuju nol. Tidak yakin apakah sudah terlambat, tapi begini saja:

function intdiv(dividend, divisor) { 
    divisor = divisor - divisor % 1;
    if (divisor == 0) throw new Error("division by zero");
    dividend = dividend - dividend % 1;
    var rem = dividend % divisor;
    return { 
        remainder: rem, 
        quotient: (dividend - rem) / divisor
    };
}

0

Jika Anda perlu menghitung sisanya untuk bilangan bulat yang sangat besar, yang runtime JS tidak dapat wakili seperti itu (bilangan bulat lebih besar dari 2 ^ 32 diwakili sebagai float dan karena itu kehilangan presisi), Anda perlu melakukan beberapa trik.

Ini sangat penting untuk memeriksa banyak kasus digit cek yang ada dalam banyak contoh kehidupan sehari-hari kita (nomor rekening bank, kartu kredit, ...)

Pertama-tama Anda memerlukan nomor Anda sebagai string (jika tidak, Anda telah kehilangan presisi dan sisanya tidak masuk akal).

str = '123456789123456789123456789'

Anda sekarang harus memisahkan string Anda menjadi bagian-bagian yang lebih kecil, cukup kecil sehingga rangkaian sisa dan seutas tali dapat muat dalam 9 digit.

digits = 9 - String(divisor).length

Persiapkan ekspresi reguler untuk memisahkan string

splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g')

Misalnya, jika digits7, regexp adalah

/.{1,7}(?=(.{7})+$)/g

Ini cocok dengan nonempty substring dengan panjang maksimum 7, yang diikuti ( (?=...)adalah lookahead positif) oleh sejumlah karakter yang kelipatan dari 7. The 'g' adalah untuk membuat ekspresi dijalankan melalui semua string, tidak berhenti pada pertandingan pertama.

Sekarang konversikan setiap bagian menjadi bilangan bulat, dan hitung sisanya dengan reduce(menambahkan kembali sisa sebelumnya - atau 0 - dikalikan dengan kekuatan 10 yang benar):

reducer = (rem, piece) => (rem * Math.pow(10, digits) + piece) % divisor

Ini akan berfungsi karena algoritme sisa "pengurangan":

n mod d = (n - kd) mod d

yang memungkinkan untuk mengganti 'bagian awal' dari representasi desimal dari angka dengan sisanya, tanpa mempengaruhi sisa akhir.

Kode akhir akan terlihat seperti:

function remainder(num, div) {
  const digits = 9 - String(div).length;
  const splitter = new RegExp(`.{1,${digits}}(?=(.{${digits}})+$)`, 'g');
  const mult = Math.pow(10, digits);
  const reducer = (rem, piece) => (rem * mult + piece) % div;

  return str.match(splitter).map(Number).reduce(reducer, 0);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.