Membulatkan ke paling banyak 2 tempat desimal (hanya jika perlu)


2759

Saya ingin membulatkan paling banyak 2 tempat desimal, tetapi hanya jika perlu .

Memasukkan:

10
1.7777777
9.1

Keluaran:

10
1.78
9.1

Bagaimana saya bisa melakukan ini dalam JavaScript?


22
Saya membuat biola dengan banyak teknik yang ditawarkan sebagai solusi di sini ... sehingga Anda dapat membandingkan: fiddle
dsdsdsdsd

37
Sepertinya tidak ada yang menyadari Number.EPSILON. Gunakan Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel

3
Untuk pembaca baru, Anda tidak dapat melakukan ini kecuali Anda memiliki hasil tipe string . Matematika floating point pada representasi biner internal angka berarti selalu ada angka yang tidak dapat direpresentasikan sebagai desimal yang rapi.
Walf

9
@cronvel Bisakah Anda menjelaskan alasan penggunaan di Number.EPSILONsini?
Bruce Sun

5
Saya jatuh ke lubang kelinci dan menguji beberapa jawaban yang lebih menarik di halaman ini (hanya halaman pertama). Ini Codepen . Petunjuk: semakin tinggi jawaban yang dimilikinya, semakin rendah peluangnya untuk berfungsi dengan baik.
Adam Jagosz

Jawaban:


3494

Menggunakan Math.round(num * 100) / 100

Sunting: untuk memastikan hal-hal seperti putaran 1.005 dengan benar, kami menggunakan

Math.round((num + Number.EPSILON) * 100) / 100


395
Meskipun ini akan bekerja untuk sebagian besar kasus, itu tidak akan bekerja untuk 1,005 yang akhirnya akan menjadi 1 bukannya 1,01
James

83
@ James Wow itu benar-benar aneh- Saya bekerja di konsol dev Chrome dan saya memperhatikan bahwa 1,005 * 100 = 100,49999999999999. Math.round (100.49999999999999) mengevaluasi ke 100, sedangkan Math.round (100.5) mengevaluasi ke 101. IE9 melakukan hal yang sama. Ini karena keanehan floating point di javascript
stinkycheeseman

153
Solusi sederhana. Untuk 2 dp, gunakan Math.round((num + 0.00001) * 100) / 100. Coba Math.round((1.005 + 0.00001) * 100) / 100danMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan

31
@ Mrkschan Mengapa itu bekerja, dan apakah itu sangat mudah untuk semua angka?
CMCDragonkai

86
Bagi Anda yang tidak mengerti teknik ini disebut scaling. Pada dasarnya apa yang dilakukan jawabannya di sini adalah membawa dua angka melintasi titik desimal mengubah angka menjadi bilangan bulat untuk menghindari semua masalah titik mengambang gila, bulatkan itu dan kemudian terjemahkan kembali menjadi seperti sebelumnya dengan membaginya dengan 100 dan Anda memiliki jawaban 2dp.
Alex_Nabu

3064

Jika nilainya adalah jenis teks:

parseFloat("123.456").toFixed(2);

Jika nilainya adalah angka:

var numb = 123.23454;
numb = numb.toFixed(2);

Ada downside yang nilai-nilai seperti 1,5 akan memberikan "1,50" sebagai output. Perbaikan yang disarankan oleh @minitech:

var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.

Sepertinya Math.roundini solusi yang lebih baik. Tapi ternyata tidak! Dalam beberapa kasus ini TIDAK akan membulat dengan benar:

Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!

toFixed () juga TIDAK akan membulat dengan benar dalam beberapa kasus (diuji di Chrome v.55.0.2883.87)!

Contoh:

parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.

1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.

Saya kira, ini karena 1,555 sebenarnya seperti float 1,55499994 di belakang layar.

Solusi 1 adalah menggunakan skrip dengan algoritma pembulatan yang diperlukan, misalnya:

function roundNumber(num, scale) {
  if(!("" + num).includes("e")) {
    return +(Math.round(num + "e+" + scale)  + "e-" + scale);
  } else {
    var arr = ("" + num).split("e");
    var sig = ""
    if(+arr[1] + scale > 0) {
      sig = "+";
    }
    return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview

CATATAN: Ini bukan solusi universal untuk semua orang. Ada beberapa algoritma pembulatan yang berbeda, implementasi Anda dapat berbeda, tergantung pada kebutuhan Anda. https://en.wikipedia.org/wiki/Rounding

Solusi 2 adalah untuk menghindari perhitungan ujung depan dan menarik nilai bulat dari server backend.


81
Pendekatan yang satu ini (theFixed) bagus, dan bekerja untuk saya, tetapi secara khusus tidak sesuai dengan permintaan asli "hanya bila perlu". (Putaran 1,5 hingga 1,50, yang mematahkan spec.)
Per Lundberg

29
Untuk persyaratan "bila perlu", lakukan ini: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım

36
parseFloat("55.555").toFixed(2)kembali "55.55"di konsol pengembang Chrome.
Levi Botelho

22
Tidak ada keuntungan menggunakan toFixed daripada Math.round; toFixed mengarah ke masalah pembulatan yang cukup sama (coba dengan 5.555 dan 1.005), tetapi lebih lambat 500x (tidak main-main) daripada Math.round ... Sepertinya jawaban @MarkG lebih akurat di sini.
Pierre

17
toFixed tidak "terkadang" mengembalikan sebuah string, ia selalu mengembalikan sebuah string.
McGuireV10

464

Kamu bisa menggunakan

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}

Saya menemukan ini di MDN . Cara mereka menghindari masalah dengan 1,005 yang disebutkan .

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57

13
@Redandro, +(val)adalah paksaan menggunakan Number(val). Menggabungkan "e-2" ke nomor menghasilkan string yang perlu dikonversi kembali ke angka.
Jack

41
Berhati-hatilah bahwa untuk pelampung besar dan kecil yang akan menghasilkan NaN sejak, misalnya + "1e-21 + 2" tidak akan diuraikan dengan benar.
Pierre

16
Anda telah 'memecahkan' masalah '1.005, tetapi memperkenalkan yang baru: sekarang, di konsol Chrome, roundToTwo(1.0049999999999999)keluar sebagai 1,01 (pasti, sejak itu 1.0049999999999999 == 1.005). Sepertinya saya bahwa float yang Anda dapatkan jika Anda mengetik num = 1.005'jelas' 'harus' dibulatkan ke 1,00, karena nilai pasti num kurang dari 1,005. Tentu saja, bagi saya nampaknya string '1,005' 'jelas' 'harus' dibulatkan ke 1,01. Fakta bahwa orang yang berbeda tampaknya memiliki intuisi yang berbeda tentang apa perilaku sebenarnya yang benar di sini adalah bagian dari mengapa itu rumit.
Mark Amery

35
Tidak ada angka (floating point) di antara 1.0049999999999999dan 1.005, jadi menurut definisi, mereka adalah nomor yang sama. Ini disebut potongan dedekind.
Azmisov

6
@Ammisov benar. Sementara 1.00499 < 1.005itu true, 1.0049999999999999 < 1.005dievaluasi menjadi false.
falconepl

146

Jawaban MarkG adalah yang benar. Berikut ini adalah ekstensi umum untuk sejumlah tempat desimal.

Number.prototype.round = function(places) {
  return +(Math.round(this + "e+" + places)  + "e-" + places);
}

Pemakaian:

var n = 1.7777;    
n.round(2); // 1.78

Tes unit:

it.only('should round floats to 2 places', function() {

  var cases = [
    { n: 10,      e: 10,    p:2 },
    { n: 1.7777,  e: 1.78,  p:2 },
    { n: 1.005,   e: 1.01,  p:2 },
    { n: 1.005,   e: 1,     p:0 },
    { n: 1.77777, e: 1.8,   p:1 }
  ]

  cases.forEach(function(testCase) {
    var r = testCase.n.round(testCase.p);
    assert.equal(r, testCase.e, 'didn\'t get right number');
  });
})

20
Pierre mengangkat masalah serius dengan jawaban MarkG.
dsjoerg

9
Catatan: Jika Anda tidak ingin mengubah Number.prototype - cukup tulis ini sebagai fungsi: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman

2
Cara keren untuk mengembangkan ini. Anda dapat memeriksa apakah desimal negatif maka balikkan e + dan e-. Kemudian n = 115; n.round (-2); akan menghasilkan 100
Lee Louviere

3
Coba n: 1e + 19 - ia mengembalikan NaN
DavidJ

3
Algoritma ini selalu mengumpulkan (bukan menjauh dari nol). Jadi, dalam hal angka negatif, outputnya tidak seperti yang Anda harapkan:(-1.005).round(2) === -1
Aleksej Komarov

123

Kamu harus menggunakan:

Math.round( num * 100 + Number.EPSILON ) / 100

Sepertinya tidak ada yang menyadari Number.EPSILON.

Juga perlu dicatat bahwa ini bukan keanehan JavaScript seperti yang dikatakan beberapa orang.

Itu hanyalah cara angka floating point bekerja di komputer. Seperti 99% bahasa pemrograman, JavaScript tidak memiliki angka floating point buatan sendiri ; itu bergantung pada CPU / FPU untuk itu. Komputer menggunakan biner, dan dalam biner, tidak ada angka seperti itu 0.1, tetapi perkiraan biner belaka untuk itu. Mengapa? Untuk alasan yang sama dari 1/3 tidak dapat ditulis dalam desimal: nilainya 0,33333333 ... dengan infinity bertiga.

Ini dia Number.EPSILON. Angka itu adalah perbedaan antara 1 dan angka berikutnya yang ada dalam angka floating point presisi ganda. Itu dia: Tidak ada angka antara 1dan 1 + Number.EPSILON.

EDIT:

Seperti yang ditanyakan dalam komentar, mari kita perjelas satu hal: menambahkan Number.EPSILONhanya relevan ketika nilai untuk dibulatkan adalah hasil dari operasi aritmatika, karena dapat menelan beberapa delta kesalahan floating point.

Ini tidak berguna ketika nilainya berasal dari sumber langsung (misalnya: literal, input atau sensor pengguna).

EDIT (2019):

Seperti @maganap dan beberapa orang tunjukkan, sebaiknya tambahkan Number.EPSILONsebelum dikalikan:

Math.round( ( num + Number.EPSILON ) * 100 ) / 100

EDIT (desember 2019):

Akhir-akhir ini, saya menggunakan fungsi yang mirip dengan ini untuk membandingkan angka epsilon-aware:

const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;

function epsilonEquals( a , b ) {
  if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
    return false ;
  }
  if ( a === 0 || b === 0 ) {
    return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
  }
  return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}

Kasus penggunaan saya adalah pernyataan + validasi data lib yang saya kembangkan selama bertahun-tahun.

Bahkan, dalam kode saya menggunakan ESPILON_RATE = 1 + 4 * Number.EPSILONdan EPSILON_ZERO = 4 * Number.MIN_VALUE(empat kali epsilon), karena saya ingin pemeriksa kesetaraan cukup longgar untuk mengumpulkan kesalahan floating point.

Sejauh ini, itu terlihat sempurna bagi saya. Saya harap ini akan membantu.


1
@palota Anda dapat mendefinisikan angka yang benar-benar seperti yang disebutkan kronil EPSILON
Daniel San

3
Ini adalah jawaban yang benar! Masalahnya sendiri terkait dengan bagaimana angka float bekerja secara internal, bukan tentang javascript
Daniel San

22
Sebenarnya, Anda harus menambahkan epsilon SEBELUM mengalikan dengan 100 Math.round( (num + Number.EPSILON) * 100) / 100.. Saya setuju juga ini adalah metode yang tepat untuk pembulatan yang benar (meskipun tidak persis apa yang diminta dalam pertanyaan ini)
maganap

2
@cronvel Hmm. Kamu benar; pilihan arah memang masuk akal. Jadi saya kira keberatan saya yang tersisa hanyalah melakukan hal ini hanya masuk akal jika Anda bekerja di domain tempat Anda memiliki alasan berprinsip untuk berpikir bahwa nilai Anda adalah "angka bulat". Jika Anda tahu bahwa input Anda adalah hasil dari operasi aritmatika sederhana pada angka dengan jumlah kecil tempat desimal, maka tentu saja, Anda mungkin hanya memiliki 0.004999999999999999akibat dari kesalahan floating point dan hasil yang benar secara matematis mungkin 0,005. Jika itu pembacaan dari sensor? Tidak terlalu banyak.
Mark Amery

1
@marchaos Ini tidak gagal: bagian selalu dibulatkan ke atas . Misalnya Math.round(1.5)= 2, tetapi Math.round(-1.5)= -1. Jadi ini sangat konsisten. Di sini -1 lebih besar dari -2, sama seperti -1000 lebih besar dari -1000.01. Jangan bingung dengan angka absolut yang lebih besar .
cronvel

84

Orang bisa menggunakan .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23

4
Juga karena ia menambahkan angka nol, yang bukan pertanyaan awal yang ditanyakan .
Alastair Maw

2
Tetapi nol trailing mudah dilucuti dengan regex, yaitu `Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Chad

1
@ Daniel yang jawabannya atas adalah sama (seperti yang sekarang) tetapi tidak alsways putaran dengan benar, coba +(1.005).toFixed(2)yang kembali 1bukan 1.01.
Emile Bergeron

3
@ChadMcElligott: Regex Anda tidak berfungsi dengan baik dengan integer: Number(9).toFixed(2).replace(/0+$/, '')=> "9."
Jacob van Lingen

Sepertinya ini tidak membulat. Itu hanya terpotong pada dasarnya. Dalam beberapa kasus, cukup baik! Terima kasih.
iedmrc

78

Pertanyaan ini rumit.

Misalkan kita memiliki fungsi,, roundTo2DP(num)yang menggunakan float sebagai argumen dan mengembalikan nilai yang dibulatkan menjadi 2 tempat desimal. Apa yang harus dievaluasi dari masing-masing ekspresi ini?

  • roundTo2DP(0.014999999999999999)
  • roundTo2DP(0.0150000000000000001)
  • roundTo2DP(0.015)

Jawaban 'jelas' adalah bahwa contoh pertama harus dibulatkan menjadi 0,01 (karena lebih dekat ke 0,01 daripada 0,02) sedangkan dua lainnya harus bulat menjadi 0,02 (karena 0,015000000000000000001 lebih dekat ke 0,02 daripada 0,01, dan karena 0,015 persis setengah jalan antara mereka dan ada konvensi matematika bahwa angka-angka seperti itu dapat ditangkap).

Tangkapannya, yang mungkin sudah Anda duga, adalah yang roundTo2DP tidak mungkin diterapkan untuk memberikan jawaban yang jelas, karena ketiga angka yang diteruskan itu adalah angka yang sama . Angka-angka floating point biner IEEE 754 (jenis yang digunakan oleh JavaScript) tidak bisa persis mewakili sebagian besar angka-angka non-integer, sehingga ketiga literal numerik di atas dibulatkan ke angka floating point terdekat yang valid. Jumlah ini, seperti yang terjadi, tepat

0.01499999999999999944488848768742172978818416595458984375

yang lebih dekat ke 0,01 daripada ke 0,02.

Anda dapat melihat bahwa ketiga angka itu sama di konsol browser Anda, Node shell, atau penerjemah JavaScript lainnya. Bandingkan saja mereka:

> 0.014999999999999999 === 0.0150000000000000001
true

Jadi ketika saya menulis m = 0.0150000000000000001, nilai pastim yang saya dapatkan lebih dekat 0.01daripada itu 0.02. Namun, jika saya mengonversi mke String ...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015

... Saya mendapatkan 0,015, yang seharusnya membulatkan ke 0,02, dan yang jelas bukan angka 56-desimal yang sebelumnya saya katakan bahwa semua angka-angka ini persis sama dengan. Jadi sihir gelap apa ini?

Jawabannya dapat ditemukan dalam spesifikasi skrip ECMAS , di bagian 7.1.12.1: ToString diterapkan pada tipe Number . Di sini aturan untuk mengubah beberapa Nomor m menjadi String ditetapkan. Bagian kuncinya adalah titik 5, di mana bilangan bulat s dihasilkan yang digitnya akan digunakan dalam representasi String m :

misalkan n , k , dan s adalah bilangan bulat sehingga k ≥ 1, 10 k -1s <10 k , nilai angka untuk s × 10 n - k adalah m , dan k adalah sekecil mungkin. Perhatikan bahwa k adalah jumlah digit dalam representasi desimal s , bahwa s tidak dapat dibagi dengan 10, dan bahwa digit paling signifikan dari s tidak selalu ditentukan secara unik oleh kriteria ini.

Bagian kuncinya di sini adalah persyaratan bahwa " k adalah sekecil mungkin". Apa yang dimaksud dengan jumlah persyaratan adalah persyaratan yang, diberikan Nomor m, nilai String(m)harus memiliki jumlah digit yang paling mungkin sambil tetap memenuhi persyaratan itu Number(String(m)) === m. Karena kita sudah tahu itu 0.015 === 0.0150000000000000001, sekarang jelas mengapa String(0.0150000000000000001) === '0.015'harus benar.

Tentu saja, tidak satu pun dari diskusi ini yang langsung menjawab apa yang roundTo2DP(m) harus dikembalikan. Jika mnilai tepatnya adalah 0,01499999999999999949488848768742172978818416595458984375, tetapi representasi String-nya adalah '0,015', lalu apa jawaban yang benar - secara matematis, praktis, filosofis, atau apa pun - ketika kita membulatkannya ke dua tempat desimal?

Tidak ada jawaban yang benar untuk ini. Itu tergantung pada kasus penggunaan Anda. Anda mungkin ingin menghormati representasi String dan membulatkan ke atas ketika:

  • Nilai yang diwakili secara inheren diskrit, misalnya jumlah mata uang dalam mata uang 3-desimal seperti dinar. Dalam hal ini, nilai sebenarnya dari Angka seperti 0,015 adalah 0,015, dan representasi 0,0149999999 ... yang didapat di titik mengambang biner adalah kesalahan pembulatan. (Tentu saja, banyak yang akan berargumen, secara wajar, bahwa Anda harus menggunakan pustaka desimal untuk menangani nilai-nilai tersebut dan tidak pernah mewakili mereka sebagai Angka titik mengambang biner.)
  • Nilai diketik oleh pengguna. Dalam kasus ini, sekali lagi, angka desimal yang dimasukkan lebih 'benar' daripada representasi titik mengambang biner terdekat.

Di sisi lain, Anda mungkin ingin menghormati nilai titik apung biner dan membulatkan ke bawah ketika nilai Anda berasal dari skala inheren terus menerus - misalnya, jika itu pembacaan dari sensor.

Kedua pendekatan ini memerlukan kode yang berbeda. Untuk menghormati representasi String dari Angka, kita dapat (dengan sedikit kode yang cukup halus) menerapkan pembulatan kita sendiri yang bertindak langsung pada representasi String, digit demi digit, menggunakan algoritma yang sama yang akan Anda gunakan di sekolah ketika Anda diajarkan bagaimana membulatkan angka. Di bawah ini adalah contoh yang menghormati persyaratan OP untuk mewakili angka ke 2 tempat desimal "hanya jika perlu" dengan menelusur membuntuti nol setelah titik desimal; Anda mungkin, tentu saja, perlu menyesuaikannya dengan kebutuhan Anda yang sebenarnya.

/**
 * Converts num to a decimal string (if it isn't one already) and then rounds it
 * to at most dp decimal places.
 *
 * For explanation of why you'd want to perform rounding operations on a String
 * rather than a Number, see http://stackoverflow.com/a/38676273/1709587
 *
 * @param {(number|string)} num
 * @param {number} dp
 * @return {string}
 */
function roundStringNumberWithoutTrailingZeroes (num, dp) {
    if (arguments.length != 2) throw new Error("2 arguments required");

    num = String(num);
    if (num.indexOf('e+') != -1) {
        // Can't round numbers this large because their string representation
        // contains an exponent, like 9.99e+37
        throw new Error("num too large");
    }
    if (num.indexOf('.') == -1) {
        // Nothing to do
        return num;
    }

    var parts = num.split('.'),
        beforePoint = parts[0],
        afterPoint = parts[1],
        shouldRoundUp = afterPoint[dp] >= 5,
        finalNumber;

    afterPoint = afterPoint.slice(0, dp);
    if (!shouldRoundUp) {
        finalNumber = beforePoint + '.' + afterPoint;
    } else if (/^9+$/.test(afterPoint)) {
        // If we need to round up a number like 1.9999, increment the integer
        // before the decimal point and discard the fractional part.
        finalNumber = Number(beforePoint)+1;
    } else {
        // Starting from the last digit, increment digits until we find one
        // that is not 9, then stop
        var i = dp-1;
        while (true) {
            if (afterPoint[i] == '9') {
                afterPoint = afterPoint.substr(0, i) +
                             '0' +
                             afterPoint.substr(i+1);
                i--;
            } else {
                afterPoint = afterPoint.substr(0, i) +
                             (Number(afterPoint[i]) + 1) +
                             afterPoint.substr(i+1);
                break;
            }
        }

        finalNumber = beforePoint + '.' + afterPoint;
    }

    // Remove trailing zeroes from fractional part before returning
    return finalNumber.replace(/0+$/, '')
}

Contoh penggunaan:

> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'

Fungsi di atas mungkin adalah apa yang ingin Anda gunakan untuk menghindari pengguna yang pernah menyaksikan angka yang mereka masukkan dibulatkan secara salah.

(Sebagai alternatif, Anda juga bisa mencoba perpustakaan round10 yang menyediakan fungsi berperilaku serupa dengan implementasi yang sangat berbeda.)

Tetapi bagaimana jika Anda memiliki Bilangan jenis kedua - nilai yang diambil dari skala berkelanjutan, di mana tidak ada alasan untuk berpikir bahwa perkiraan representasi desimal dengan lebih sedikit tempat desimal lebih akurat daripada yang memiliki lebih banyak? Dalam hal ini, kami tidak ingin menghormati representasi String, karena representasi itu (seperti yang dijelaskan dalam spesifikasi) sudah semacam sorting; kami tidak ingin membuat kesalahan dengan mengatakan "0,014999999 ... 375 putaran hingga 0,015, yang bulat hingga 0,02, jadi 0,014999999 ... 375 putaran hingga 0,02".

Di sini kita cukup menggunakan toFixedmetode bawaan. Perhatikan bahwa dengan memanggil Number()String yang dikembalikan oleh toFixed, kita mendapatkan Nomor yang representasi String-nya tidak memiliki angka nol (berkat cara JavaScript menghitung representasi String dari Angka, yang dibahas sebelumnya dalam jawaban ini).

/**
 * Takes a float and rounds it to at most dp decimal places. For example
 *
 *     roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
 *
 * returns 1.234
 *
 * Note that since this treats the value passed to it as a floating point
 * number, it will have counterintuitive results in some cases. For instance,
 * 
 *     roundFloatNumberWithoutTrailingZeroes(0.015, 2)
 *
 * gives 0.01 where 0.02 might be expected. For an explanation of why, see
 * http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
 * roundStringNumberWithoutTrailingZeroes function there instead.
 *
 * @param {number} num
 * @param {number} dp
 * @return {number}
 */
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
    var numToFixedDp = Number(num).toFixed(dp);
    return Number(numToFixedDp);
}

Ini tidak berfungsi dalam beberapa kasus tepi: coba ( jsfiddle ) dengan roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Hasil yang diharapkan (seperti dalam PHP echo round(362.42499999999995, 2)): 362.43. Hasil aktual:362.42
Dr. Gianluigi Zane Zanettini

1
@ Dr.GianluigiZaneZanettini Huh. Aneh. Saya tidak yakin mengapa PHP roundmemberi 362,43. Tampaknya secara intuitif salah, karena 362.4249999999999995 kurang dari 362.425 (dalam matematika dan kode - 362.42499999999995 < 362.425benar dalam JS dan PHP). Jawaban PHP juga tidak meminimalkan jarak antara angka floating point asli dan bulat, sejak 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. Menurut php.net/manual/en/function.round.php , PHP roundmengikuti standar C99; Saya harus menjelajah ke tanah C untuk memahami apa yang terjadi.
Mark Amery

1
Setelah melihat implementasi pembulatan dalam sumber PHP , saya tidak tahu apa yang dilakukannya. Ini adalah implementasi yang sangat rumit penuh dengan makro dan cabang dan konversi string dan bercabang pada nilai-nilai presisi sihir hard-coded. Saya tidak yakin apa yang harus dikatakan di luar "Jawaban PHP salah, dan kita harus mengajukan laporan bug". Bagaimana Anda menemukan nomor 362.42499999999995, omong-omong?
Mark Amery

1
@ Dr.GianluigiZaneZanettini Saya telah membuat laporan bug: bugs.php.net/bug.php?id=75644
Mark Amery

2
@ Dr.GianluigiZaneZanettini "Apakah saya masuk akal?" - tidak; bukan itu cara pembulatan. 1,000005 berakhir dalam lima, tetapi jika dibulatkan ke bilangan bulat terdekat jawabannya harus 1, bukan 2. Demikian juga, 1499995 berakhir dalam lima, tetapi jika dibulatkan ke jutaan terdekat, hasilnya harus 1000000, bukan 2000000. Dalam kasus 362.42499999999995 dibulatkan menjadi 2 DP, yang harus menentukan arah pembulatan adalah tempat desimal ketiga , yaitu 4.
Mark Amery

76

Pertimbangkan .toFixed()dan .toPrecision():

http://www.javascriptkit.com/javatutors/formatnumber.shtml


1
toFixed menambahkan titik desimal ke setiap nilai apa pun yang terjadi.
stinkycheeseman

13
Keduanya tidak berguna di sini
Esailija

Sayangnya, kedua fungsi tersebut akan menambahkan tempat desimal ekstra yang tampaknya tidak diinginkan @stinkycheeseman.
jackwanders


2
mereka mengembalikan string dan bukan angka, sehingga mereka memformat dan tidak menghitung ..
saimiris_devel

63

Metode pembulatan yang tepat. Sumber: Mozilla

(function(){

    /**
     * Decimal adjustment of a number.
     *
     * @param   {String}    type    The type of adjustment.
     * @param   {Number}    value   The number.
     * @param   {Integer}   exp     The exponent (the 10 logarithm of the adjustment base).
     * @returns {Number}            The adjusted value.
     */
    function decimalAdjust(type, value, exp) {
        // If the exp is undefined or zero...
        if (typeof exp === 'undefined' || +exp === 0) {
            return Math[type](value);
        }
        value = +value;
        exp = +exp;
        // If the value is not a number or the exp is not an integer...
        if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
            return NaN;
        }
        // Shift
        value = value.toString().split('e');
        value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
        // Shift back
        value = value.toString().split('e');
        return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    }

    // Decimal round
    if (!Math.round10) {
        Math.round10 = function(value, exp) {
            return decimalAdjust('round', value, exp);
        };
    }
    // Decimal floor
    if (!Math.floor10) {
        Math.floor10 = function(value, exp) {
            return decimalAdjust('floor', value, exp);
        };
    }
    // Decimal ceil
    if (!Math.ceil10) {
        Math.ceil10 = function(value, exp) {
            return decimalAdjust('ceil', value, exp);
        };
    }
})();

Contoh:

// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50

Seseorang meletakkan ini di GitHub dan npm juga: github.com/jhohlfeld/round10
Jo Liss

Nah, Math.round10(3544.5249, -2)mengembalikan 3544.52 sebagai gantinya 3544.53
Matija

2
@Matija Wolfram alpha mengatakan 3544.52 juga. Anda ingin meminimalkan kesalahan antara angka saat ini dan perkiraan bulat. Perkiraan terdekat 3544.5249ke 2 tempat desimal adalah 3544.52(kesalahan = 0,0049). Jika ya 3544.53, kesalahannya adalah 0,0051. Anda melakukan pembulatan berturut-turut yaitu Math.round10 (Math.round10 (3544.5249, -3), -2) yang memberikan kesalahan pembulatan yang lebih besar dan karenanya tidak diinginkan.
pengguna

3
@Matija, dari sudut pandang matematika, 3544.5249 bulat adalah 3544.52, bukan 3544.53, jadi kode ini benar. Jika Anda ingin membulatkannya menjadi 3544,53 dalam hal ini dan dalam kasus-kasus seperti ini (walaupun sulit tidak tepat), lakukan sesuatu seperti ini:number += 0.00011
Bozidar Sikanjic

@Matija: Saya pikir fungsinya berfungsi sebagaimana mestinya. Mungkin Anda ingin mengulangi pembulatan seperti:Math.round10( Math.round10(3544.5249, -3) , -2)
jumxozizi

60

Tidak ada jawaban yang ditemukan di sini benar . @stinkycheeseman meminta untuk mengumpulkan , Anda semua membulatkan angka.

Untuk mengumpulkan, gunakan ini:

Math.ceil(num * 100)/100;

15
Contoh input dan output menunjukkan bahwa meskipun pertanyaan mengatakan 'mengumpulkan ...' sebenarnya dimaksudkan untuk 'bulat menjadi ...'.
JayDM

2
@stinkycheeseman menunjukkan kesalahan dalam kasus tertentu, dia tidak ingin selalu mengumpulkan seperti halnya langit, dia hanya ingin 0,005 untuk menambah hingga 0,01
mjaggard

9
Ditemukan bug aneh saat menguji Math.ceil(1.1 * 100)/100;-it kembali 1.11, karena 1.1 * 100 110.00000000000001sesuai dengan browser modern baru Firefox, Chrome, Safari dan Opera ... IE, dengan cara lama, masih berpikir 1.1*100=1100.
skobaljic

1
@skobaljic tryMath.ceil(num.toFixed(4) * 100) / 100
treeface

1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100juga akan kembali 1.11di Firefox, masalah / bug browser modern adalah dengan multiplikasi dan orang-orang harus mengetahuinya (saya bekerja pada permainan lotere waktu itu misalnya).
skobaljic

47

Berikut ini cara mudah untuk melakukannya:

Math.round(value * 100) / 100

Anda mungkin ingin melanjutkan dan membuat fungsi terpisah untuk melakukannya untuk Anda:

function roundToTwo(value) {
    return(Math.round(value * 100) / 100);
}

Maka Anda cukup memberikan nilai.

Anda bisa meningkatkannya menjadi angka desimal acak dengan menambahkan parameter kedua.

function myRound(value, places) {
    var multiplier = Math.pow(10, places);

    return (Math.round(value * multiplier) / multiplier);
}

2
Solusi ini salah, lihat stackoverflow.com/questions/38322372/... jika Anda memasukkan 156893.145 dan membulatkannya dengan fungsi di atas, Anda mendapatkan 156893.14 alih-alih 156893.15 !!!
saimiris_devel

2
@saimiris_devel, Anda telah berhasil menemukan contoh di mana representasi angka angka dalam JavaScript gagal. Anda benar bahwa putarannya salah. Tapi - itu karena jumlah sampel Anda ketika dikalikan dengan 100 sudah rusak (15689314.499999998). Sungguh, jawaban berfitur lengkap akan membutuhkan perpustakaan yang telah dikembangkan secara khusus untuk memperhitungkan ketidakkonsistenan dalam penanganan JavaScript pada bilangan real. Kalau tidak, Anda mungkin bisa membatalkan salah satu jawaban yang telah diberikan untuk pertanyaan ini.
JayDM

36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12

1
Ini tidak akan selalu memberikan hasil yang sama dengan yang Anda dapatkan jika Anda mengambil representasi String dari Nomor Anda dan membulatkannya. Sebagai contoh +(0.015).toFixed(2) == 0.01,.
Mark Amery

35

Bagi saya Math.round () tidak memberikan jawaban yang benar. Saya menemukan toFixed (2) bekerja lebih baik. Di bawah ini adalah contoh keduanya:

console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer


Penting untuk dicatat bahwa toFixed tidak melakukan pembulatan, dan bahwa Math.round hanya membulatkan ke bilangan bulat terdekat. Untuk mempertahankan desimal, oleh karena itu kita perlu mengalikan angka asli dengan jumlah kekuatan sepuluh yang nolnya mewakili jumlah desimal yang Anda inginkan, dan kemudian membagi hasilnya dengan angka yang sama. Dalam kasus Anda: Math.round (43000/80000 * 100 * 100) / 100. Akhirnya toFixed (2) dapat diterapkan untuk memastikan bahwa selalu ada dua desimal dalam hasil (dengan trailing nol di mana diperlukan) - sempurna untuk meluruskan serangkaian angka yang disajikan secara vertikal :)
Turbo

7
Juga penting untuk dicatat bahwa .toFIxed () menampilkan string, bukan angka.
carpiediem

2
Ini masih belum menyelesaikan pembulatan 1.005. (1.005).toFixed(2)masih menghasilkan 1.00.
DPac

34

Gunakan fungsi ini Number(x).toFixed(2);


8
Bungkus semuanya Numberlagi, jika Anda tidak ingin itu dikembalikan sebagai string:Number(Number(x).toFixed(2));

5
The Numberpanggilan tidak perlu, x.toFixed(2)bekerja.
bgusach

3
@ bgusach Panggilan nomor diperlukan, karena pernyataan x.toFixed (2) mengembalikan string dan bukan nomor. Untuk mengkonversi lagi ke angka kita perlu membungkus dengan Nomor
Mohan Ram

2
Ketika menggunakan metode ini (1).toFixed(2)kembali 1.00, tetapi penanya diperlukan 1dalam kasus ini.
Eugene Mala

1
Ini tidak berhasil, 1.005.toFixed(2)menghasilkan "1"kapan seharusnya "1.01".
Adam Jagosz

33

2017
Cukup gunakan kode asli.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"

Jika Anda perlu ketat dan menambahkan angka hanya jika diperlukan dapat digunakan replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');

3
Metode toFixed mengembalikan string. Jika Anda menginginkan hasil angka, Anda harus mengirim hasil toFixed ke parseFloat.
Zambonilli

@Zambonilli Atau kalikan 1 jika perlu. tetapi karena angka tetap, sebagian besar case adalah untuk tampilan dan bukan untuk string perhitungan adalah format yang tepat
pery mimon

2
-1; tidak hanya toFixeddisarankan oleh beberapa jawaban bertahun-tahun sebelum jawaban Anda, tetapi gagal memenuhi persyaratan "hanya jika perlu" dalam pertanyaan; (1).toFixed(2)memberi "1.00"tempat yang diinginkan si penanya "1".
Mark Amery

OK mengerti. Saya menambahkan beberapa solusi juga untuk kasus itu
pery mimon

Jika Anda menggunakan lodash, ini bahkan lebih mudah: _.round (angka, decimalPlace) Menghapus komentar terakhir saya, karena ada masalah. Lodash _.round TIDAK berfungsi, meskipun. 1,005 dengan tempat desimal 2 yang dikonversi ke 1,01.
Devin Fields

32

Coba solusi ringan ini :

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222

Adakah yang tahu jika ada perbedaan antara ini dan return Number(x.toFixed(digits))?

1
@ JoRocc ... seharusnya tidak ada bedanya sejauh yang saya lihat karena .toFixed()hanya memungkinkan untuk angka.
petermeissner

4
Jawaban ini memiliki masalah yang sama dengan yang disebutkan beberapa kali di halaman ini. Cobalah round(1.005, 2)dan melihat hasil dari 1bukan 1.01.
MilConDoin

tampaknya lebih merupakan masalah pembulatan algo? - ada lebih dari yang dibayangkan: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner

31

Ada beberapa cara untuk melakukan itu. Bagi orang-orang seperti saya, varian Lodash

function round(number, precision) {
    var pair = (number + 'e').split('e')
    var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
    pair = (value + 'e').split('e')
    return +(pair[0] + 'e' + (+pair[1] - precision))
}

Pemakaian:

round(0.015, 2) // 0.02
round(1.005, 2) // 1.01

Jika proyek Anda menggunakan jQuery atau lodash, Anda juga dapat menemukan roundmetode yang tepat di perpustakaan.

Perbarui 1

Saya menghapus varian n.toFixed(2), karena itu tidak benar. Terima kasih @ avalanche1


Opsi kedua akan mengembalikan string dengan tepat dua titik desimal. Pertanyaannya hanya meminta titik desimal jika perlu. Opsi pertama lebih baik dalam hal ini.
Marcos Lima

@MarcosLima Number.toFixed()akan mengembalikan string tetapi dengan simbol plus sebelum itu, JS interpreter akan mengonversi string menjadi angka. Ini adalah gula sintaksis.
stanleyxu2005

Di Firefox, alert((+1234).toFixed(2))tampilkan "1234.00".
Marcos Lima

Di Firefox, alert(+1234.toFixed(2))melempar SyntaxError: identifier starts immediately after numeric literal. Saya tetap dengan opsi 1.
Marcos Lima

Ini tidak berfungsi dalam beberapa kasus tepi: coba ( jsfiddle ) dengan 362.42499999999995. Hasil yang diharapkan (seperti dalam PHP echo round(362.42499999999995, 2)): 362.43. Hasil aktual:362.42
Dr. Gianluigi Zane Zanettini

26

Jika Anda menggunakan pustaka lodash, Anda bisa menggunakan metode bundar lodash seperti berikut.

_.round(number, precision)

Misalnya:

_.round(1.7777777, 2) = 1.78

@ Peter Set fungsionalitas yang disediakan Lodash sangat bagus dibandingkan dengan Javascript standar. Namun, saya mendengar bahwa Lodash memiliki beberapa masalah kinerja dibandingkan dengan JS standar. codeburst.io/...
Madura Pradeep

1
Saya menerima pendapat Anda bahwa ada kekurangan kinerja dengan menggunakan lodash. Saya pikir masalah-masalah itu biasa terjadi pada banyak abstraksi. Tapi lihat saja berapa banyak jawaban yang ada di utas ini dan bagaimana solusi intuitif gagal untuk kasus tepi. Kami telah melihat pola ini dengan jQuery dan masalah root diselesaikan ketika browser mengadopsi standar umum yang memecahkan sebagian besar kasus penggunaan kami. Kemacetan kinerja kemudian dipindahkan ke mesin peramban. Saya pikir hal yang sama harus terjadi pada lodash. :)
Peter

26

Karena ES6 ada cara yang 'tepat' (tanpa mengesampingkan statika dan membuat solusi) untuk melakukan ini dengan menggunakan toPrecision

var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));

var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));

var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));

maka Anda bisa adil parseFloatdan nol akan 'pergi'.

console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));

Itu tidak memecahkan 'masalah pembulatan 1,005' karena - karena itu intrinsik dengan bagaimana pecahan float sedang diproses .

console.log(1.005 - 0.005);

Jika Anda terbuka untuk perpustakaan, Anda dapat menggunakan bignumber.js

console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));

console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script>


3
(1.005).toPrecision(3)masih kembali, 1.00bukan 1.01sebenarnya.
Giacomo

toPrecisionmengembalikan string yang mengubah tipe output yang diinginkan.
adamduren

@ Giacomo Ini bukan cacat .toPrecisionmetode, ini adalah kekhususan angka floating-point (yang angka dalam JS) - coba 1.005 - 0.005, itu akan kembali 0.9999999999999999.
shau-kote

1
(1).toPrecision(3)mengembalikan '1,00', tetapi penanya ingin memiliki 1dalam kasus ini.
Eugene Mala

1
Seperti @Giacomo katakan, jawaban ini tampaknya membingungkan "angka signifikan" dengan "pembulatan ke sejumlah tempat desimal". toPrecisionApakah format, bukan yang terakhir, dan bukan jawaban untuk pertanyaan OP, meskipun mungkin tampak relevan pada awalnya itu mendapat banyak kesalahan. Lihat en.wikipedia.org/wiki/Significant_figures . Misalnya Number(123.4).toPrecision(2)pengembalian "1.2e+2"dan Number(12.345).toPrecision(2)pengembalian "12". Saya juga setuju dengan poin @ adamduren bahwa ia mengembalikan sebuah string yang tidak diinginkan (bukan masalah besar tapi tidak diinginkan).
Neek

23

MarkG dan Lavamantis menawarkan solusi yang jauh lebih baik daripada yang telah diterima. Sayang sekali mereka tidak mendapatkan lebih banyak suara!

Berikut adalah fungsi yang saya gunakan untuk memecahkan masalah desimal floating point juga berdasarkan MDN . Itu bahkan lebih umum (tapi kurang ringkas) daripada solusi Lavamantis:

function round(value, exp) {
  if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);

  value = +value;
  exp  = +exp;

  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));

  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}

Gunakan dengan:

round(10.8034, 2);      // Returns 10.8
round(1.275, 2);        // Returns 1.28
round(1.27499, 2);      // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46

Dibandingkan dengan solusi Lavamantis, kita dapat melakukan ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123

2
Solusi Anda tidak mencakup beberapa kasus yang bertentangan dengan solusi MDN. Meskipun mungkin lebih pendek, itu tidak akurat ...
astorije

1
ronde (-1835.665,2) => -1835.66
Jorge Sampayo


18

Pendekatan termudah adalah menggunakan toFixed dan kemudian menghapus angka nol menggunakan fungsi Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78

ini tidak berfungsi untuk semua kasus. lakukan tes ekstensif sebelum mengirim jawaban.
baburao

@ baburao Silakan memposting kasus di mana solusi di atas tidak bekerja
Marcin Wanago

angka const = 15; Number (number.toFixed (2)); //15.00 bukannya 15
Kevin Jhangiani

1
@KevinJhangiani const number = 15; Number (number.toFixed (2)); // 15 - Saya mengujinya di Chrome dan Firefox terbaru
Marcin Wanago

@KevinJhangiani, bagaimana Anda dapatkan 15.00? Angka dalam JS tidak menyimpan tempat desimal dan tampilan apa pun secara otomatis memotong kelebihan tempat desimal (angka nol di bagian akhir).
VLAZ

16
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) di sini 2 adalah jumlah digit hingga kami ingin membulatkan angka ini.


.toFixed () ini lebih mudah diimplementasikan. cukup lewati sekali saja.
Ritesh Dhuri


14

Cara termudah:

+num.toFixed(2)

Ini mengubahnya menjadi string, dan kemudian kembali ke integer / float.


Terima kasih atas jawaban paling sederhana ini. Namun, apa yang '+' dalam + num? Itu tidak bekerja untuk saya di mana val desimal datang dalam string. Saya lakukan: (num * 1) .toFixed (2).
Ethan

@momo hanya mengubah argumen toFixed()ke 3. Jadi +num.toFixed(3). Itu bekerja dengan cara yang seharusnya, 1,005 dibulatkan menjadi 1,00, yang sama dengan 1
bigpotato

1
@ Edmund Seharusnya mengembalikan 1,01, bukan 1,00
mmm

13

Berikut adalah metode prototipe:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);

13

Gunakan sesuatu seperti ini "parseFloat (parseFloat (value) .toFixed (2))"

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1

1
tidak jika ketidaktepatan intrinsik dengan representasi float. Anda hanya akan menghapusnya dan kemudian memperkenalkan kembali kesalahan yang sama dengan mengonversi kembali ke float lagi!
Ben McIntyre

12

Salah satu cara untuk mencapai pembulatan seperti itu hanya jika perlu adalah dengan menggunakan Number.prototype.toLocaleString () :

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})

Ini akan memberikan persis output yang Anda harapkan, tetapi sebagai string. Anda masih bisa mengonversi angka itu kembali ke angka jika itu bukan tipe data yang Anda harapkan.


Ini adalah solusi terbersih yang ada sejauh ini dan menghindari semua masalah floating point yang rumit, tetapi per dukungan MDN masih belum lengkap - Safari belum mendukung argumen yang lewat toLocaleString.
Mark Amery

@MarkAmery Untuk saat ini, hanya Android Browser memiliki beberapa masalah: caniuse.com/#search=toLocaleString
ptyskju

12

Setelah menjalankan berbagai iterasi dari semua cara yang mungkin untuk mencapai presisi pembulatan desimal akurat, jelas bahwa solusi yang paling akurat dan efisien adalah dengan menggunakan Number.EPSILON. Ini memberikan solusi matematika yang benar untuk masalah presisi matematika floating point. Hal ini dapat dengan mudah diisi polif seperti yang ditunjukkan di sini: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON untuk mendukung semua pengguna IE yang tersisa terakhir (sekali lagi mungkin kita harus berhenti melakukan itu).

Diadaptasi dari solusi yang disediakan di sini: https://stackoverflow.com/a/48850944/6910392

Solusi penurunan sederhana yang memberikan pembulatan desimal, lantai, dan langit-langit yang akurat, dengan variabel presisi opsional tanpa menambahkan seluruh perpustakaan.

UPDATE: Seperti yang dicatat Sergey dalam komentar, ada batasan untuk metode ini (atau apa pun) yang pantas untuk ditunjukkan. Dalam hal angka-angka seperti 0,014999999999999999, Anda masih akan mengalami ketidakakuratan yang merupakan hasil dari memukul tepi mutlak keterbatasan akurasi untuk penyimpanan nilai floating point. Tidak ada matematika atau solusi lain yang dapat diterapkan untuk menjelaskan itu, karena nilai itu sendiri segera dievaluasi sebagai 0,015. Anda dapat mengonfirmasi ini dengan hanya memanggil nilai itu sendiri di konsol. Karena keterbatasan ini, bahkan tidak mungkin untuk menggunakan manipulasi string untuk mengurangi nilai ini, karena representasi stringnya hanyalah "0,015". Solusi apa pun untuk memperhitungkan ini perlu diterapkan secara logis pada sumber data sebelum pernah menerima nilai ke dalam skrip,

var DecimalPrecision = (function(){
        if (Number.EPSILON === undefined) {
            Number.EPSILON = Math.pow(2, -52);
        }
        this.round = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.round((n + r) * o) / o;
        }
        this.ceil = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.ceil((n + r) * o) / o;
        }
        this.floor = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.floor((n + r) * o) / o;
        }
        return this;
    })();
    console.log(DecimalPrecision.round(1.005));
    console.log(DecimalPrecision.ceil(1.005));
    console.log(DecimalPrecision.floor(1.005));
    console.log(DecimalPrecision.round(1.0049999));
    console.log(DecimalPrecision.ceil(1.0049999));
    console.log(DecimalPrecision.floor(1.0049999));
    console.log(DecimalPrecision.round(2.175495134384,7));
    console.log(DecimalPrecision.round(2.1753543549,8));
    console.log(DecimalPrecision.round(2.1755465135353,4));


1
(DecimalPrecision.round (0,014999999999999999, 2)) // mengembalikan 0,02
Sergey

Tangkapan yang bagus! Masalahnya adalah dengan penyimpanan floating point di JS, akan selalu ada beberapa kasus tepi. Berita baiknya adalah matematika yang Anda terapkan pada Number.EPSILON pertama dapat lebih disesuaikan untuk mendorong case edge lebih jauh ke edge. Jika Anda ingin menjamin tidak ada kemungkinan untuk kasus tepi, satu-satunya solusi nyata Anda akan menjadi manipulasi string, dan kemudian matematika. Saat Anda melakukan perhitungan matematis apa pun pada nilainya (bahkan dalam mencoba memindahkan desimal), maka Anda telah menghasilkan bug.
KFish

Sebenarnya, pada pemeriksaan lebih lanjut, ini bukan karena matematika yang terlibat, melainkan masalah memanifestasikan segera setelah meminta nilai yang ditentukan. Anda dapat mengonfirmasi ini hanya dengan mengetikkan angka itu ke konsol dan melihat bahwa itu langsung dievaluasi menjadi 0,015. Oleh karena itu, ini akan mewakili tepi mutlak akurasi untuk setiap angka floating point di JS. Dalam hal ini Anda bahkan tidak dapat mengonversi ke string dan memanipulasi karena nilai string akan menjadi "0,015"
KFish

11

Ini adalah solusi paling sederhana dan lebih elegan (dan saya yang terbaik di dunia;):

function roundToX(num, X) {    
    return +(Math.round(num + "e+"+X)  + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9

4
Itu cara yang bagus untuk menulis ulang jawaban yang diterima untuk menerima argumen menggunakan Enotasi.
AxelH

1
Ini tidak berfungsi dalam beberapa kasus tepi: coba ( jsfiddle ) roundToX(362.42499999999995, 2). Hasil yang diharapkan (seperti dalam PHP echo round(362.42499999999995, 2)): 362.43. Hasil aktual:362.42
Dr. Gianluigi Zane Zanettini

6
IMHO, hasil PHP Anda salah. Tidak peduli apa yang muncul setelah desimal ketiga, jika desimal ketiga lebih rendah dari 5, maka desimal kedua harus tetap sama. Itulah definisi matematika.
Soldeplata Saketos

1
Untuk menjadi lebih ringkas, "e +" bisa saja menjadi "e".
Lonnie Best
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.