19 April 2021

Angka

Dalam JavaScript modern, ada dua tipe angka:

  1. Angka regular di JavaScript yang disimpan dalam format 64-bit IEEE-754, juga dikenal sebagai “angka double precision floating point”. Inilah angka yang kita paling sering kita pakai, dan kita akan bahas tentang mereka di bab ini.

  2. Angka BigInt, untuk mewakili integer dengan panjang sembarang. Mereka kadang dibutuhkan, karena angka regular tak bisa lebih dari 253 atau kurang dari -253. Karena bigint dipakai di sedikit area spesial, kita khususkan mereka bab spesial BigInt.

Jadi di sini kita akan bahas angka regular. Ayo perluas pengetahuan kita tentang mereka.

Cara lain menulis angka

Bayangkan kita harus menulis 1 milyar. Cara jelasnya begini:

let billion = 1000000000;

Kita juga bisa menggunakan _ sebagai pemisahnya:

let billion = 1_000_000_000;

Di sini, garis bawah _ memainkan peran sebagai “syntactic sugar”, ini membuat angka lebih mudah dibaca. Mesin JavaScript mengabaikan _ di antara digit, jadi nilainya sama persis dengan satu miliar di atas.

Tapi di kehidupan nyata, kita biasanya menghindari menulis string nol yang panjang karena rentan terjadi kesalahan. Selain itu, kita malas. Kita biasanya akan menulis sesuatu seperti "1bn" untuk milyar atau "7.3bn" untuk 7 milyar 300 juta. Sama halnya dengan angka besar lainnya.

Di JavaScript, kita perpendek angka dengan menambah huruf "e" ke angka dan menspesifikasi jumlah nol:

let billion = 1e9;  // 1 milyar, literalnya: 1 dan 9 nol

alert( 7.3e9 );  // 7.3 milyar (7,300,000,000)

Dengan kata lain, "e" kalikan angkanya dengan 1 dengan jumlah nol yang diberikan.

1e3 = 1 * 1000 // e3 means *1000
1.23e6 = 1.23 * 1000000 // e6 means *1000000

Sekarang ayo tulis sesuatu lebih kecil. Katakan, 1 microsecond (sepersejuta second):

let ms = 0.000001;

Sama seperti sebelumnya, memakai "e" bisa membantu. Jika kita ingin menghindari menulis nol eksplisit, kita bisa katakan hal yang sama:

let ms = 1e-6; // enam nol di sebelah kiri dari 1

Jika kita hitung nol di 0.000001, ada 6 dari mereka. Jadi alaminya 1e-6.

Dengan kata lain, angka negatif setelah "e" artinya pembagian 1 dengan jumlah nol yang diberikan:

// -3 membagi 1 dengan 3 nol
1e-3 = 1 / 1000 (=0.001)

// -6 membagi 1 dengan 6 nol
1.23e-6 = 1.23 / 1000000 (=0.00000123)

Hex, angka binary dan octal

Angka Hexadecimal secara luas dipakai di JavaScript untuk mewakili warna, encode karakter, dan banyak hal lain. Alaminya, ada cara lebih singkat menulis mereka: 0x kemudian angkanya.

Misalnya:

alert( 0xff ); // 255
alert( 0xFF ); // 255 (sama, case diabaikan)

Sistem numeral binary dan octal jarang dipakai, tapi juga didukung menggunakan prefix 0b dan 0o:

let a = 0b11111111; // bentuk binary dari 255
let b = 0o377; // bentuk octal dari 255

alert( a == b ); // true, angka sama 255 dari kedua sisi

Cuma ada 3 sistem numeral dengan dukungan begitu. Untuk sistem numeral lain, kita sebaiknya memakai fungsi parseInt (yang akan kita lihat nanti di bab ini).

toString(base)

Metode num.toString(base) mengembalikan representasi string dari num di sistem numeral dengan base yang diberikan.

Misalnya:

let num = 255;

alert( num.toString(16) );  // ff
alert( num.toString(2) );   // 11111111

base bisa bervariasi dari 2 hingga 36. Defaultnya 10.

Penggunaan umumnya ialah:

  • base=16 dipakai untuk warna hex, character encoding dll, digit bisa 0..9 atau A..F.

  • base=2 paling banyak untuk mendebug operasi bitwise, digit bisa 0 atau 1.

  • base=36 ini maximum, digit bisa 0..9 atau A..Z. Seluruh alfabet latin dipakai untuk merepresentasi angka. Hal lucu, tapi berguna untuk 36 ialah saat kita harus mengubah identifier numerik panjang ke dalam sesuatu yang lebih pendek, misalnya untuk membuat url pendek. Bisa direpresentasikan dalam sistem 36:

    alert( 123456..toString(36) ); // 2n9c
Dua dot untuk memanggil metode

Tolong ingat bahwa dua dot di 123456..toString(36) bukan typo. Jika kita mau memanggil langsung metode pada angka, seperti toString di contoh di atas, maka kita harus menaruh dua dot .. setelahnya.

Jika kita menaruh dot tunggal: 123456.toString(36), maka akan ada galat, karena syntax JavaScript berimplikasi bahwa bagian desimal setelah dot pertama. Dan jika kita menaruh satu dot lagi, maka JavaScript tahu bahwa bagian desimal kosong dan sekarang pergi ke metode.

Juga bisa menulis (123456).toString(36).

Pembulatan

Satu dari operasi paling banyak dipakai saat bekerja dengan angka ialah pembulatan.

Ada beberapa fungsi built-in untuk pembulatan:

Math.floor
Membulat ke bawah: 3.1 menjadi 3, dan -1.1 menjadi -2.
Math.ceil
Membulat ke atas: 3.1 menjadi 4, dan -1.1 menjadi -1.
Math.round
Membulatkan ke bilangan bulat terdekat: 3.1 menjadi3, 3.6 menjadi4, huruf tengah: 3.5 juga dibulatkan ke atas menjadi4.
Math.trunc (tidak didukung oleh Internet Explorer)
Menghapus apa pun setelah koma desimal tanpa pembulatan: 3.1 menjadi3, -1.1 menjadi-1.

Ini tabel untuk meringkas perbedaan di antara mereka:

Math.floor Math.ceil Math.round Math.trunc
3.1 3 4 3 3
3.6 3 4 4 3
-1.1 -2 -1 -1 -1
-1.6 -2 -1 -2 -1

Fungsi ini membahas semua cara yang mungkin untuk berhadapan dengan bagian desimal dari angka. Tapi bagaimana jika kita mau membulatkan angka ke digit ke-n setelah desimal?

Misalnya, kita punya 1.2345 dan mau membulatkan ke 2 digit, memperoleh 1.23.

Ada dua cara melakukannya:

  1. Kali-dan-bagi.

    Misalnya, untuk membulatkan angka ke digit kedua setelah desimal, kita bisa mengalikan angkanya dengan 100 (atau sebuah pangkat dari 10), panggil fungsi pembulatan lalu membagi itu kembali.

    let num = 1.23456;
    
    alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  2. Metode toFixed(n) Membulatkan ke digit n setelah poin itu dan mengembalikan representasi string dari hasilnya.

    let num = 12.34;
    alert( num.toFixed(1) ); // "12.3"

    Ini membulatkan ke atas atau ke bawah ke nilai terdekat, serupa dengan Math.round:

    let num = 12.36;
    alert( num.toFixed(1) ); // "12.4"

    Silakan catat hasil dari toFixed ialah string. Jika bagian desimal lebih pendek dari yang dibutuhkan, nol ditambahkan di akhir:

    let num = 12.34;
    alert( num.toFixed(5) ); // "12.34000", tambah nol supaya tepat 5 digit

    Kita bisa mengkonversi itu ke angka menggunakan unary plus atau panggilan Number(): +num.toFixed(5).

Kalkulasi yang tidak tepat

Secara internal, angka direpresentasikan dalam format 64-bit IEEE-754, jadi ada tepat 64 bit untuk menyimpan angka: 52 di antaranya digunakan untuk menyimpan angka, 11 di antaranya menyimpan posisi titik desimal (nol untuk angka bilangan bulat), dan 1 bit untuk tanda.

Jika angka terlalu besar, itu akan meluapkan penyimpanan 64-bit, berpotensi memberikan infinity:

alert( 1e500 ); // Infinity

Yang mungkin agak kurang jelas, tetapi sering terjadi adalah hilangnya ketepatan.

Pertimbangkan (falsy!) tes ini:

alert( 0.1 + 0.2 == 0.3 ); // false

Benar, jika kita memeriksa jumlah dari 0.1 dan 0.2 adalah 0.3, kita mendapatkan false.

Aneh! Kenapa hasilnya itu dan tidak 0.3?

alert( 0.1 + 0.2 ); // 0.30000000000000004

Aduh! Ada lebih banyak konsekuensi daripada perbandingan yang salah di sini. Bayangkan Anda membuat situs e-shopping dan pengunjung memasukkan barang-barang $ 0,10 dan$ 0,20 ke troli mereka. Total pesanan akan $ 0,30000000000000004. Itu akan mengejutkan siapa pun.

Tetapi kenapa hal ini bisa terjadi?

Sebuah angka disimpan di memori dalam bentuk binary, sebuah urutan dari bits – satu dan nol. Tetapi bilangan pecahan seperti 0.1, 0.2 yang terlihat sederhana dalam sistem angka desimal sebenarnya adalah pecahan tak berujung dalam bentuk binernya.

Dengan kata lain, apa itu 0,1? Ini adalah satu dibagi dengan sepuluh 1 / 10, sepersepuluh. Dalam sistem angka desimal, angka-angka seperti itu mudah diwakili. Bandingkan dengan sepertiga: 1 / 3. Ini menjadi pecahan yang tak berujung 0,33333 (3).

Jadi, pembagian dengan kekuatan 10 dijamin bekerja dengan baik dalam sistem desimal, tetapi pembagian dengan 3 tidak. Untuk alasan yang sama, dalam sistem angka biner, pembagian dengan kekuatan 2 dijamin bekerja, tetapi 1 / 10 menjadi fraksi biner tanpa akhir.

Tidak ada cara untuk menyimpan * tepat 0,1 * atau * persis 0,2 * menggunakan sistem biner, sama seperti tidak ada cara untuk menyimpan sepertiga sebagai fraksi desimal.

Format numerik IEEE-754 memecahkan ini dengan membulatkan ke angka terdekat yang mungkin. Aturan pembulatan ini biasanya tidak memungkinkan kita untuk melihat bahwa “kehilangan presisi kecil”, tetapi itu ada.

Kita bisa melihat ini dalam aksi:

alert( 0.1.toFixed(20) ); // 0.10000000000000000555

Dan ketika kita menjumlahkan dua angka, “kehilangan presisi” mereka bertambah.

Itu sebabnya 0,1 + 0,2 tidak sepenuhnya0,3.

Tidak hanya JavaScript

Masalah yang sama ada di banyak bahasa pemrograman lainnya.

PHP, Java, C, Perl, Ruby memberikan hasil yang sama persis, karena mereka didasarkan pada format numerik yang sama.

Bisakah kita mengatasi masalah ini? Tentu, metode yang paling dapat diandalkan adalah melengkapi hasilnya dengan bantuan metode toFixed(n):

let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30

Harap dicatat bahwa toFixed selalu mengembalikan string. Ini memastikan bahwa ia memiliki 2 digit setelah titik desimal. Itu sebenarnya nyaman jika kita memiliki e-shopping dan perlu menunjukkan $ 0,30. Untuk kasus lain, kita dapat menggunakan plus unary untuk memaksanya menjadi nomor:

let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3

Kita juga dapat sementara mengalikan angka dengan 100 (atau angka yang lebih besar) untuk mengubahnya menjadi bilangan bulat, menghitung, dan kemudian membaginya kembali. Kemudian, saat kita mengerjakan matematika dengan bilangan bulat, kesalahannya agak berkurang, tetapi kita masih mendapatkannya di pembagian:

alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001

Jadi, pendekatan perkalian/pembagian mengurangi kesalahan, tetapi tidak menghapusnya sama sekali.

Terkadang kita bisa mencoba menghindari pecahan sama sekali. Seperti jika kita berurusan dengan toko, maka kita dapat menyimpan harga dalam sen, bukan dalam dolar. Tetapi bagaimana jika kita menerapkan diskon 30%? Dalam praktiknya, menghindari pecahan sama sekali jarang dimungkinkan. Hanya bulatkan mereka untuk memotong “ekor” bila diperlukan.

Lucunya

Coba jalankan ini:

// Halo! saya adalah angka yang meningkat sendiri!
alert( 9999999999999999 ); // menunjukkan 10000000000000000

Penderitaan ini berasal dari masalah yang sama: kehilangan presisi. Ada 64 bit untuk angka, 52 di antaranya dapat digunakan untuk menyimpan digit, tetapi itu tidak cukup. Jadi digit yang paling tidak penting menghilang.

JavaScript tidak memicu kesalahan dalam kejadian semacam itu. Itu melakukan yang terbaik untuk memasukkan angka ke dalam format yang diinginkan, tetapi sayangnya, format ini tidak cukup besar.

Dua nol

Konsekuensi lucu yang lain dari representasi internal dari angka adalah adanya dua nol: 0 dan-0.

Itu karena sebuah tanda diwakili oleh satu bit, sehingga dapat diatur atau tidak diatur untuk angka apa pun termasuk nol.

Dalam kebanyakan kasus perbedaannya tidak terlalu mencolok, karena operator cocok untuk memperlakukan mereka sebagai sesuatu yang sama.

Tests: isFinite dan isNaN

Ingat dua nilai numerik khusus ini?

  • Infinity (dan -Infinity) adalah nilai numerik khusus yang lebih besar (kurang) dari apa pun.
  • NaN mewakili kesalahan.

Mereka termasuk dalam tipe angka, tetapi bukan angka “normal”, jadi ada fungsi khusus untuk memeriksanya:

  • isNaN(value) mengubah argumennya menjadi angka dan kemudian mengujinya NaN:

    alert( isNaN(NaN) ); // true
    alert( isNaN("str") ); // true

    Tetapi apakah kita membutuhkan fungsi ini? Tidak bisakah kita menggunakan perbandingan === NaN? Maaf, tapi jawabannya adalah tidak. Nilai NaN unik karena tidak sama dengan apa pun, termasuk dirinya sendiri:

    alert( NaN === NaN ); // false
  • isFinite(value) mengonversi argumennya menjadi angka dan mengembalikan true jika itu angka biasa, bukan NaN/Infinity/-Infinity:

    alert( isFinite("15") ); // true
    alert( isFinite("str") ); // false, karena nilai khusus: NaN
    alert( isFinite(Infinity) ); // false, karena nilai khusus: Infinity

Terkadang isFinite digunakan untuk memvalidasi apakah sebuah nilai string adalah sebuah angka reguler:

let num = +prompt("Enter a number", '');

// akan benar kecuali jika Anda memasukkan Infinity, -Infinity atau bukan angka
alert( isFinite(num) );

Harap dicatat bahwa string kosong atau spasi-saja diperlakukan sebagai 0 dalam semua fungsi numerik termasukisFinite.

Dibandingkan dengan Object.is

Ada metode bawaan khusus Object.is yang membandingkan nilai seperti ===, tetapi lebih dapat diandalkan untuk dua kasus:

  1. Ini bekerja dengan NaN: Object.is(NaN, NaN) === true, itu hal yang bagus.
  2. Nilai 0 and -0 adalah berbeda: Object.is(0, -0) === false, secara teknis adalah benar, karena secara internal nomor tersebut memiliki bit tanda yang mungkin berbeda bahkan jika semua bit lainnya nol.

Pada kasus lain, Object.is(a, b) adalah sama dengan a === b.

Cara perbandingan ini sering digunakan dalam spesifikasi JavaScript. Ketika suatu algoritma internal perlu membandingkan dua nilai untuk menjadi persis sama, ia menggunakan Object.is (secara internal disebut SameValue).

parseInt dan parseFloat

Konversi angka menggunakan nilai tambah + atau Number() sangat ketat. Jika suatu nilai bukan angka, itu gagal:

alert( +"100px" ); // NaN

Satu-satunya pengecualian adalah spasi di awal atau di akhir string, karena diabaikan.

Tetapi dalam kehidupan nyata kita sering memiliki nilai dalam satuan, seperti "100px" atau "12pt" dalam CSS. Juga di banyak negara simbol mata uang mengikuti jumlah, jadi kita memiliki "€19" dan ingin mengekstraksi nilai numerik dari itu.

Untuk itulah parseInt danparseFloat ada.

Mereka “membaca” angka dari sebuah string sampai mereka tidak bisa. Jika terjadi kesalahan, nomor yang dikumpulkan dikembalikan. Fungsi parseInt mengembalikan integer, sementaraparseFloat akan mengembalikan nomor floating-point:

alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12, hanya bagian integer yang dikembalikan
alert( parseFloat('12.3.4') ); // 12.3, poin kedua berhenti membaca

Ada situasi dimana parseInt/parseFloat akan mengembalikan NaN. Ini terjadi ketika tidak ada digit yang bisa dibaca

alert( parseInt('a123') ); // NaN, symbol pertama menghentikan proses
Pernyataan kedua dari parseInt(str, radix)

Fungsi parseInt() fungsi memiliki parameter opsional kedua. Ini menentukan dasar dari sistem angka, jadi parseInt juga dapat mengurai string nomor hex, angka biner dan sebagainya:

alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255, tanpa 0x juga bekerja

alert( parseInt('2n9c', 36) ); // 123456

Fungsi matematika lainnya

Javascript memiliki objek Math bawaan dimana berisi perpustakaan kecil fungsi matematika dan konstanta.

Beberapa contoh:

Math.random()

Mengembalikan angka acak dari 0 hingga 1 (tidak termasuk 1)

alert( Math.random() ); // 0.1234567894322s
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (angka aca apa saja)
Math.max(a, b, c...) / Math.min(a, b, c...)

Mengembalikan argumen terbesar / terkecil dari jumlah argumen yang arbitrer.

alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1
Math.pow(n, power)

mengembalikan bilangan n yang dipangkatkan.

alert( Math.pow(2, 10) ); // 2 pangkat 10 = 1024

Ada lebih banyak fungsi dan konstanta dalam objek Math, termasuk trigonometri, yang dapat Anda temukan di docs untuk objek Math.

Kesimpulan

Untuk menulis angka dengan banyak nol:

  • Tambahkan "e" dengan hitungan angka nol ke angka. Seperti: 123e6 sama dengan 123 dengan 6 nol 123000000.
  • Angka negatif setelah "e" menyebabkan angka untuk dibagi 1 dengan nol yang diberikan. Contohnya 123e-6 berarti 0.000123 (123 millionths).

Untuk sistem angka yang berbeda:

  • Dapat menulis angka secara langsung dalam sistem hex (0x), oktal (0o) dan sistem biner (0b)
  • parseInt(str, base) mem-parsing string str menjadi bilangan bulat dalam sistem angka dengan diberikan base, 2 ≤ base ≤ 36.
  • num.toString(base) mengonversi angka menjadi string dalam sistem angka dengan diberikan base.

Untuk mengkonversi nilai seperti 12pt dan 100px menjadi sebuah angka:

  • Gunakan parseInt/parseFloat untuk konversi “lembut”, dimana membaca sebuah angka dari string dan mengembalikan nilai yang bisa dibaca sebelum terjadi kesalahan.

Untuk pecahan:

  • Bulangan menggunakan Math.floor, Math.ceil, Math.trunc, Math.round atau num.toFixed(presisi).
  • Pastikan untuk mengingat ada kehilangan presisi saat bekerja dengan pecahan.

Lebih banyak fungsi matematika:

  • Lihat objek Math ketika Anda membutuhkannya. Perpustakaannya sangat kecil, tetapi dapat memenuhi kebutuhan dasar.

Tugas

Buat skrip yang meminta pengunjung untuk memasukkan dua angka dan kemudian menunjukkan jumlah mereka.

jalankan demonya

N.B. Ada gotcha dengan tipe.

let a = +prompt("The first number?", "");
let b = +prompt("The second number?", "");

alert( a + b );

Catatan bahwa unary plus + sebelum prompt. Segera mengkonversi nilai ke angka.

Jika tidak, a dan b akan menjadi string jumlah mereka akan menjadi gabungan mereka, yaitu: "1" + "2" = "12".

Berdasarkan dokumentasi Math.round dan toFixed keduanya membulatkan ke angka terdekat: 0..4 turun sementara 5..9 naik.

Contohnya:

alert( 1.35.toFixed(1) ); // 1.4

Dalam contoh serupa di bawah ini, mengapa 6.35 dibulatkan menjadi 6.3, dan tidak 6.4?

alert( 6.35.toFixed(1) ); // 6.3

Bagaimana untuk membulatkan 6.35 dengan benar?

Secara internal pecahan desimal 6.35 adalah sebuah biner tanpa akhir. Seperti biasa dalam kasus seperti ini, disimpan dengan kehilangan presisi.

Ayo lihat:

alert( 6.35.toFixed(20) ); // 6.34999999999999964473

Kehilangan presisi dapat menyebabkan peningkatan dan penurunan angka. Dalam kasus khusus ini jumlahnya menjadi sedikit lebih sedikit, itu sebabnya dibulatkan.

Dan apa untuk 1.35?

alert( 1.35.toFixed(20) ); // 1.35000000000000008882

Di sini kehilangan presisi membuat jumlahnya sedikit lebih besar, jadi itu dibulatkan.

Bagaimana kita dapat memperbaiki masalah dengan 6.35 jika kita ingin itu dibulatkan dengan cara yang benar?

Kita harus membawanya lebih dekat ke integer sebelum pembulatan:

alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000

Perhatikan bahwa 63.5 tidak memiliki kehilangan presisi sama sekali. Itu karena bagian desimal 0,5 sebenarnya1 / 2. Pecahan yang dibagi oleh kekuatan 2 persis diwakili dalam sistem biner, sekarang kita dapat membulatkannya:

alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4

Buatlah sebuah fungsi readNumber yang meminta (prompts) nomor hingga pengunjung memasukkan nilai numerik yang valid.

Nilai yang dihasilkan harus dikembalikan sebagai angka.

Pengunjung juga dapat menghentikan proses dengan memasukkan baris kosong atau menekan “BATAL”. Dalam hal ini, fungsi tersebut harus mengembalikan null.

jalankan demonya

Buka sandbox dengan tes.

function readNumber() {
  let num;

  do {
    num = prompt("Enter a number please?", 0);
  } while ( !isFinite(num) );

  if (num === null || num === '') return null;

  return +num;
}

alert(`Read: ${readNumber()}`);

Solusinya sedikit lebih rumit dari itu karena kita perlu menangani null/baris kosong.

Jadi, kita benar-benar menerima input hingga ini merupakan “angka reguler”. Baik null (cancel) maupun baris kosong juga cocok dengan kondisi itu, karena dalam bentuk numerik mereka adalah0.

Setelah kita berhenti, kita perlu memperlakukan null dan khususnya baris kosong (mengembalikan null), karena mengonversinya menjadi angka akan mengembalikan 0.

Buka solusi dengan tes di sandbox.

Loop ini tidak terbatas. Tidak pernah berakhir. Mengapa?

let i = 0;
while (i != 10) {
  i += 0.2;
}

Itu karena i tidak akan pernah sebanding dengan 10.

Jalankan ini untuk melihat nilai real dari i:

let i = 0;
while (i < 11) {
  i += 0.2;
  if (i > 9.8 && i < 10.2) alert( i );
}

Tidak satu pun dari mereka yang benar-benar 10.

Hal-hal seperti itu terjadi karena kehilangan presisi ketika menambahkan pecahan seperti 0,2.

Kesimpulan: menghindari pemeriksaan kesetaraan saat bekerja dengan pecahan desimal.

Fungsi bawaan Math.random() membuat sebuah angka acak dari 0 ke 1 (tidak termasuk 1).

Tulis fungsi random(min, max) untuk menghasilkan angka floating-point acak dari min kemax (tidak termasuk max).

Contoh kerjanya:

alert( random(1, 5) ); // 1.2345623452
alert( random(1, 5) ); // 3.7894332423
alert( random(1, 5) ); // 4.3435234525

Kita perlu “memetakan” semua nilai dari interval 0…1 ke dalam nilai dari min kemax.

Itu bisa dilakukan dalam dua tahap:

  1. Jika kita mengalikan angka acak dari 0…1 dengan max-min, maka interval nilai yang mungkin meningkat0..1 ke 0..max-min.
  2. Sekarang jika kita menambahkan min, interval yang mungkin menjadi darimin ke max.

Fungsi:

function random(min, max) {
  return min + Math.random() * (max - min);
}

alert( random(1, 5) );
alert( random(1, 5) );
alert( random(1, 5) );

Buatlah sebuah fungsi randomInteger(min, max) yang menghasilkan angka integer acak dari min ke max termasuk keduanya min dan max sebagai nilai yang mungkin.

Angka apa pun dari interval min..max harus muncul dengan probabilitas yang sama.

Contoh kerjanya:

alert( randomInteger(1, 5) ); // 1
alert( randomInteger(1, 5) ); // 3
alert( randomInteger(1, 5) ); // 5

Anda dapat menggunakan solusi dari tugas sebelumnya sebagai basis.

The simple but wrong solution

The simplest, but wrong solution would be to generate a value from min to max and round it:

function randomInteger(min, max) {
  let rand = min + Math.random() * (max - min);
  return Math.round(rand);
}

alert( randomInteger(1, 3) );

The function works, but it is incorrect. The probability to get edge values min and max is two times less than any other.

If you run the example above many times, you would easily see that 2 appears the most often.

That happens because Math.round() gets random numbers from the interval 1..3 and rounds them as follows:

values from 1    ... to 1.4999999999  become 1
values from 1.5  ... to 2.4999999999  become 2
values from 2.5  ... to 2.9999999999  become 3

Now we can clearly see that 1 gets twice less values than 2. And the same with 3.

The correct solution

There are many correct solutions to the task. One of them is to adjust interval borders. To ensure the same intervals, we can generate values from 0.5 to 3.5, thus adding the required probabilities to the edges:

function randomInteger(min, max) {
  // now rand is from  (min-0.5) to (max+0.5)
  let rand = min - 0.5 + Math.random() * (max - min + 1);
  return Math.round(rand);
}

alert( randomInteger(1, 3) );

An alternative way could be to use Math.floor for a random number from min to max+1:

function randomInteger(min, max) {
  // here rand is from min to (max+1)
  let rand = min + Math.random() * (max + 1 - min);
  return Math.floor(rand);
}

alert( randomInteger(1, 3) );

Now all intervals are mapped this way:

values from 1  ... to 1.9999999999  become 1
values from 2  ... to 2.9999999999  become 2
values from 3  ... to 3.9999999999  become 3

All intervals have the same length, making the final distribution uniform.

Peta tutorial

komentar

baca ini sebelum berkomentar…
  • Jika Anda memiliki saran apa yang harus ditingkatkan - silakan kunjungi kirimkan Github issue atau pull request sebagai gantinya berkomentar.
  • Jika Anda tidak dapat memahami sesuatu dalam artikel – harap jelaskan.
  • Untuk menyisipkan beberapa kata kode, gunakan tag <code>, untuk beberapa baris – bungkus dengan tag <pre>, untuk lebih dari 10 baris – gunakan sandbox (plnkr, jsbin, < a href='http://codepen.io'>codepen…)