Menggunakan bitwise ATAU 0 untuk membuat angka

192

Seorang kolega saya menemukan metode untuk meratakan angka float menggunakan bitwise atau:

var a = 13.6 | 0; //a == 13

Kami membicarakannya dan bertanya-tanya beberapa hal.

  • Bagaimana cara kerjanya? Teori kami adalah bahwa menggunakan operator seperti itu melemparkan angka ke bilangan bulat, sehingga menghilangkan bagian fraksional
  • Apakah ada kelebihan dibandingkan melakukannya Math.floor? Mungkin ini sedikit lebih cepat? (pun tidak dimaksudkan)
  • Apakah ada kekurangannya? Mungkin itu tidak berhasil dalam beberapa kasus? Kejelasan itu jelas, karena kami harus mengetahuinya, dan yah, saya menulis pertanyaan ini.

Terima kasih.

Alex Turpin
sumber
6
Kerugian: hanya bekerja hingga 2 ^ 31−1 yaitu sekitar 2 miliar (10 ^ 9). Nilai Nomor maks adalah sekitar 10 ^ 308 btw.
Šime Vidas
12
Contoh: 3000000000.1 | 0mengevaluasi ke -1294967296. Jadi metode ini tidak dapat diterapkan untuk perhitungan uang (terutama dalam kasus di mana Anda mengalikan dengan 100 untuk menghindari angka desimal).
Šime Vidas
13
@ ŠimeVidas Mengapung tidak boleh digunakan dalam perhitungan uang juga
George Reith
20
Ini bukan lantai, itu memotong (pembulatan ke 0).
Bartłomiej Zalewski
3
@setelah mencoba mengetikkan 0.1 + 0.2 == 0.3konsol JavaScript. Jika bahasa Anda mendukungnya, Anda harus menggunakan tipe desimal. Jika tidak, simpan sen sebagai gantinya.
Alex Turpin

Jawaban:

160

Bagaimana cara kerjanya? Teori kami adalah bahwa menggunakan operator seperti itu melemparkan angka ke bilangan bulat, sehingga menghilangkan bagian fraksional

Semua operasi bitwise kecuali pergeseran kanan yang tidak ditandatangani >>>,, bekerja pada bilangan bulat 32-bit yang telah ditandatangani. Jadi menggunakan operasi bitwise akan mengubah float menjadi integer.

Apakah ada kelebihan dibandingkan mengerjakan Math.floor? Mungkin ini sedikit lebih cepat? (pun tidak dimaksudkan)

http://jsperf.com/or-vs-floor/2 tampaknya sedikit lebih cepat

Apakah ada kekurangannya? Mungkin itu tidak berhasil dalam beberapa kasus? Kejelasan itu jelas, karena kami harus mengetahuinya, dan yah, saya menulis pertanyaan ini.

  • Tidak akan lulus jsLint.
  • Bilangan bulat bertanda 32-bit saja
  • Perilaku Komparatif Ganjil:, Math.floor(NaN) === NaNsementara(NaN | 0) === 0
Joe
sumber
9
@ Harvest memang, karena itu tidak bulat, hanya memotong.
Alex Turpin
5
Kerugian lain yang mungkin adalah bahwa Math.floor(NaN) === NaN, sementara (NaN | 0) === 0. Perbedaan itu mungkin penting dalam beberapa aplikasi.
Ted Hopp
4
Jsperf Anda menghasilkan informasi kinerja untuk loop kosong pada chrome karena gerakan kode invarian loop. Tes perf yang sedikit lebih baik adalah: jsperf.com/floor-performance/2
Sam Giles
4
Ini adalah bagian standar asm.js(tempat saya pertama kali mempelajarinya). Lebih cepat jika tanpa alasan lain karena itu tidak memanggil fungsi pada Mathobjek, fungsi yang bisa kapan saja diganti seperti Math.floor = function(...).
GM
3
(value | 0) === valuedapat digunakan untuk memeriksa bahwa suatu nilai sebenarnya adalah bilangan bulat dan hanya bilangan bulat (seperti dalam kode sumber Elm @ dwayne-penjahat yang ditautkan). Dan foo = foo | 0dapat digunakan untuk memaksa nilai apa pun ke integer (di mana angka 32-bit terpotong dan semua non-angka menjadi 0).
David Michael Gregg
36

Ini pemotongan karena bertentangan dengan lantai. Jawaban Howard agak benar; Tapi saya akan menambahkan bahwa Math.floortidak persis apa yang seharusnya sehubungan dengan angka negatif. Secara matematis, itulah lantai itu.

Dalam kasus yang Anda jelaskan di atas, programmer lebih tertarik memotong atau memotong desimal sepenuhnya. Meskipun, sintaks yang mereka gunakan agak mengaburkan fakta bahwa mereka mengubah float menjadi int.

Chad La Guardia
sumber
7
Ini adalah jawaban yang benar, yang diterima tidak. Tambahkan ke sana yang Math.floor(8589934591.1)menghasilkan hasil yang diharapkan, 8589934591.1 | 0 JANGAN .
Salman A
21

Dalam ECMAScript 6, padanannya |0adalah Math.trunc , jenis yang harus saya katakan:

Mengembalikan bagian integral dari angka dengan menghapus digit fraksional apa pun. Itu hanya memotong titik dan digit di belakangnya, tidak peduli apakah argumennya adalah angka positif atau angka negatif.

Math.trunc(13.37)   // 13
Math.trunc(42.84)   // 42
Math.trunc(0.123)   //  0
Math.trunc(-0.123)  // -0
Math.trunc("-1.123")// -1
Math.trunc(NaN)     // NaN
Math.trunc("foo")   // NaN
Math.trunc()        // NaN
zangw
sumber
6
Kecuali kenyataan bahwa Math.trunc()bekerja dengan angka lebih tinggi atau sama dengan 2 ^ 31 dan | 0tidak
Nolyurn
10

Poin pertama Anda benar. Angka dilemparkan ke bilangan bulat dan dengan demikian setiap angka desimal dihapus. Harap dicatat, bahwa Math.floorputaran ke bilangan bulat berikutnya menuju minus tak terhingga dan dengan demikian memberikan hasil yang berbeda ketika diterapkan pada angka negatif.

Howard
sumber
5

Javascript mewakili Numbersebagai angka mengambang ganda presisi 64-bit .

Math.floor bekerja dengan ini dalam pikiran.

Operasi bitwise bekerja dalam bilangan bulat bertanda 32bit . Bilangan bulat 32bit yang ditandatangani menggunakan bit pertama sebagai penanda negatif dan 31 bit lainnya adalah angka. Karena itu, angka min dan maks yang diizinkan adalah angka 32bit yang ditandatangani adalah -2.147.483.648 dan 2147483647 (0x7FFFFFFFFFF), masing-masing.

Jadi, ketika Anda melakukannya | 0, pada dasarnya Anda lakukan & 0xFFFFFFFF. Ini berarti, angka apa pun yang direpresentasikan sebagai 0x80000000 (2147483648) atau lebih besar akan kembali sebagai angka negatif.

Sebagai contoh:

 // Safe
 (2147483647.5918 & 0xFFFFFFFF) ===  2147483647
 (2147483647      & 0xFFFFFFFF) ===  2147483647
 (200.59082098    & 0xFFFFFFFF) ===  200
 (0X7FFFFFFF      & 0xFFFFFFFF) ===  0X7FFFFFFF

 // Unsafe
 (2147483648      & 0xFFFFFFFF) === -2147483648
 (-2147483649     & 0xFFFFFFFF) ===  2147483647
 (0x80000000      & 0xFFFFFFFF) === -2147483648
 (3000000000.5    & 0xFFFFFFFF) === -1294967296

Juga. Operasi bitwise jangan "lantai". Mereka memotong , yang sama dengan mengatakan, mereka berkeliling paling dekat 0. Setelah Anda berputar ke angka negatif, Math.floorbulatkan ke bawah saat bitwise mulai membulatkan ke atas .

Seperti yang saya katakan sebelumnya, Math.floorlebih aman karena beroperasi dengan angka mengambang 64bit. Bitwise lebih cepat , ya, tetapi terbatas pada lingkup 32bit yang ditandatangani.

Untuk meringkas:

  • Bitwise berfungsi sama jika Anda bekerja dari 0 to 2147483647.
  • Bitwise adalah nomor 1 jika Anda bekerja dari -2147483647 to 0.
  • Bitwise benar-benar berbeda untuk angka yang kurang dari -2147483648dan lebih besar dari 2147483647.

Jika Anda benar - benar ingin mengubah kinerja dan menggunakan keduanya:

function floor(n) {
    if (n >= 0 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    if (n > -0x80000000 && n < 0) {
      return (n - 1) & 0xFFFFFFFF;
    }
    return Math.floor(n);
}

Hanya untuk menambahkan Math.trunckarya seperti operasi bitwise. Jadi Anda bisa melakukan ini:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}
ShortFuse
sumber
5
  • Spesifikasi mengatakan bahwa itu dikonversi ke integer:

    Biarkan lnum menjadi ToInt32 (lval).

  • Kinerja: ini telah diuji di jsperf sebelumnya.

catatan: tautan mati ke spec dihapus

pimvdb
sumber