Arrotondare al massimo a 2 decimali (solo se necessario)


2759

Vorrei arrotondare al massimo 2 cifre decimali, ma solo se necessario .

Ingresso:

10
1.7777777
9.1

Produzione:

10
1.78
9.1

Come posso farlo in JavaScript?


22
Ho fatto un violino con molte delle tecniche offerte come soluzioni qui ... quindi puoi confrontare: violino
dsdsdsdsd

37
Nessuno sembra esserne consapevole Number.EPSILON. Usa Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel

3
Per i nuovi lettori, non puoi farlo se non hai un risultato di tipo stringa . La matematica in virgola mobile su rappresentazioni binarie interne di numeri significa che ci sono sempre numeri che non possono essere rappresentati come decimali accurati.
Walf,

9
@cronvel Puoi spiegare il motivo dell'utilizzo Number.EPSILONqui?
Bruce Sun,

5
Sono caduto nella tana del coniglio e ho testato alcune delle risposte più interessanti in questa pagina (solo prima pagina). Ecco un Codepen . Suggerimento: più voti ha la risposta, minori sono le possibilità che funzioni correttamente.
Adam Jagosz,

Risposte:


3495

Uso Math.round(num * 100) / 100

Modifica: per garantire cose come 1.005 round correttamente, usiamo

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


395
Mentre questo funzionerà per la maggior parte dei casi, non funzionerà per 1.005 che finirà per essere 1 anziché 1,01
James

83
@James Wow, è davvero strano- Sto lavorando nella console di sviluppo di Chrome e sto notando che 1.005 * 100 = 100.49999999999999. Math.round (100.49999999999999) valuta 100, mentre Math.round (100.5) valuta 101. IE9 fa la stessa cosa. Ciò è dovuto alla stranezza in virgola mobile in javascript
puzzolente

153
Una soluzione semplice. Per 2 dp, utilizzare Math.round((num + 0.00001) * 100) / 100. Prova Math.round((1.005 + 0.00001) * 100) / 100eMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan,

31
@mrkschan Perché funziona, ed è infallibile per tutti i numeri?
CMCDragonkai

86
Per quelli di voi che non capiscono questa tecnica si chiama ridimensionamento. Fondamentalmente ciò che la risposta fa qui è portare due cifre attraverso il punto decimale trasformando la figura in un numero intero per evitare tutti i pazzi problemi in virgola mobile, arrotondarlo e poi tradurlo nuovamente in quello che era prima dividendo per 100 e hai il tuo risposta a 2dp.
Alex_Nabu,

3064

Se il valore è un tipo di testo:

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

Se il valore è un numero:

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

C'è un rovescio della medaglia che valori come 1.5 daranno "1.50" come output. Una correzione suggerita da @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.

Sembra che Math.roundsia una soluzione migliore. Ma non è! In alcuni casi NON si arrotonderà correttamente:

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

toFixed () inoltre NON arrotonderà correttamente in alcuni casi (testato in Chrome v.55.0.2883.87)!

Esempi:

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.

Immagino, questo perché 1.555 è in realtà qualcosa di simile al galleggiante 1.55499994 dietro le quinte.

La soluzione 1 consiste nell'utilizzare uno script con l'algoritmo di arrotondamento richiesto, ad esempio:

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

NOTA: questa non è una soluzione universale per tutti. Esistono diversi algoritmi di arrotondamento, l'implementazione può essere diversa, dipende dalle tue esigenze. https://en.wikipedia.org/wiki/Rounding

La soluzione 2 consiste nell'evitare i calcoli del front-end e estrarre i valori arrotondati dal server back-end.


81
Questo approccio (toFixed) è buono e ha funzionato per me, ma in particolare non è conforme alla richiesta originale di "solo quando necessario". (Arrotonda da 1,5 a 1,50, il che rompe le specifiche.)
Per Lundberg,

29
Per il requisito "quando necessario", procedere come segue: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım

36
parseFloat("55.555").toFixed(2)ritorna "55.55"nella console di sviluppo di Chrome.
Levi Botelho,

22
Non vi è alcun vantaggio nell'utilizzare toFixed invece di Math.round; toFixed porta agli stessi problemi di arrotondamento (provali con 5.555 e 1.005), ma è come 500x (senza scherzi) più lento di Math.round ... Sembra che la risposta di @MarkG sia la più accurata qui.
Pierre,

17
toFixed non "a volte" restituisce una stringa, restituisce sempre una stringa.
McGuireV10,

464

Puoi usare

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

L'ho trovato su MDN . Il loro modo evita il problema con 1.005 menzionato .

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

13
@Redsandro, +(val)è l'equivalente di coercizione dell'uso Number(val). La concatenazione di "e-2" in un numero ha comportato una stringa che doveva essere riconvertita in un numero.
Jack,

41
Attenzione che per i galleggianti piccoli e grandi che produrrebbero NaN poiché, ad esempio + "1e-21 + 2" non verrà analizzato correttamente.
Pierre,

16
Hai "risolto" il problema "1.005", ma ne hai introdotto uno nuovo: ora, nella console di Chrome, roundToTwo(1.0049999999999999)esce come 1,01 (inevitabilmente, da allora 1.0049999999999999 == 1.005). Mi sembra che il float che ottieni se digiti num = 1.005"ovviamente" dovrebbe essere arrotondato a 1,00, poiché il valore esatto di num è inferiore a 1,005. Naturalmente, mi sembra anche che la stringa "1.005" "ovviamente" "debba" essere arrotondata a 1,01. Il fatto che persone diverse sembrano avere intuizioni diverse su quale sia il comportamento corretto attuale è parte del perché è complicato.
Mark Amery,

35
Non c'è un numero (in virgola mobile) tra 1.0049999999999999e 1.005, quindi per definizione, sono lo stesso numero. Questo si chiama taglio del diavolo.
Azmisov,

6
@Azmisov ha ragione. Mentre lo 1.00499 < 1.005è true, 1.0049999999999999 < 1.005valuta false.
falconepl,

146

La risposta di MarkG è quella corretta. Ecco un'estensione generica per qualsiasi numero di cifre decimali.

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

Uso:

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

Test unitario:

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 ha sollevato un serio problema con la risposta di MarkG.
dsjoerg,

9
Nota: se non si desidera modificare il Numero. Prototipo, è sufficiente scrivere questo come una funzione: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman

2
Un modo fantastico per espandere questo. È possibile verificare se il decimale è negativo, quindi invertire e + ed e-. Quindi n = 115; n.round (-2); produrrebbe 100
Lee Louviere il

3
Prova n: 1e + 19 - restituisce NaN
DavidJ il

3
Questo algoritmo arrotonda sempre (invece di allontanarsi da zero). Quindi, in caso di numeri negativi, l'output non è quello che ci si potrebbe aspettare:(-1.005).round(2) === -1
Aleksej Komarov

123

Dovresti usare:

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

Nessuno sembra esserne consapevole Number.EPSILON .

Vale anche la pena notare che questa non è una stranezza JavaScript come alcune persone hanno affermato.

Questo è semplicemente il modo in cui i numeri in virgola mobile funzionano in un computer. Come il 99% dei linguaggi di programmazione, JavaScript non ha numeri in virgola mobile fatti in casa ; si basa sulla CPU / FPU per questo. Un computer utilizza binario e in binario non ci sono numeri come0.1 , ma una semplice approssimazione binaria per quello. Perché? Per lo stesso motivo di 1/3 non può essere scritto in decimale: il suo valore è 0,33333333 ... con un infinito di tre.

Arrivano Number.EPSILON. Quel numero è la differenza tra 1 e il numero successivo esistente nei numeri in virgola mobile a precisione doppia. Ecco fatto: non esiste un numero compreso tra 11 e 1 Number.EPSILON.

MODIFICARE:

Come richiesto nei commenti, chiariamo una cosa: aggiungere Number.EPSILON è rilevante solo quando il valore da arrotondare è il risultato di un'operazione aritmetica, in quanto può ingoiare delta di errore in virgola mobile.

Non è utile quando il valore proviene da una fonte diretta (ad esempio: letterale, input dell'utente o sensore).

EDIT (2019):

Come hanno sottolineato @maganap e alcuni popoli, è meglio aggiungere Number.EPSILONprima di moltiplicare:

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

EDIT (dicembre 2019):

Ultimamente, utilizzo una funzione simile a questa per confrontare i numeri compatibili con epsilon:

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 ;
}

Il mio caso d'uso è un'asserzione + lib di convalida dei dati che sto sviluppando da molti anni.

In effetti, nel codice che sto usando ESPILON_RATE = 1 + 4 * Number.EPSILONe EPSILON_ZERO = 4 * Number.MIN_VALUE(quattro volte il epsilon), perché voglio un correttore di uguaglianza abbastanza libero da accumulare errori in virgola mobile.

Finora mi sembra perfetto. Spero che sarà di aiuto.


1
@palota potresti definire un numero veramente a denti come menziona la cronvel di EPSILON
Daniel San

3
Questa è la risposta esatta! Il problema stesso è correlato al modo in cui i numeri float funzionano internamente, non su javascript
Daniel San,

22
In realtà, devi aggiungere epsilon PRIMA di moltiplicare per 100 Math.round( (num + Number.EPSILON) * 100) / 100.. Sono anche d'accordo che questo è il metodo giusto per arrotondare correttamente (anche se non è esattamente quello che è stato chiesto in questa domanda).
maganap,

2
@cronvel Hmm. Hai ragione; la scelta della direzione ha senso. Quindi suppongo che la mia restante obiezione sia solo che farlo ha senso solo se stai lavorando in un dominio in cui hai una ragione di principio per pensare che il tuo valore sia un "numero rotondo". Se sai che il tuo input è il risultato di semplici operazioni aritmetiche su numeri con piccoli numeri di cifre decimali, allora sicuramente avrai solo 0.004999999999999999il risultato di un errore in virgola mobile e il risultato matematicamente corretto è probabilmente 0,005. Se è una lettura da un sensore? Non così tanto.
Mark Amery,

1
@marchaos Non fallisce: le metà sono sempre arrotondate per eccesso . Ad esempio Math.round(1.5)= 2, ma Math.round(-1.5)= -1. Quindi questo è perfettamente coerente. Qui -1 è maggiore di -2, proprio come -1000 è maggiore di -1000,01. Da non confondere con numeri assoluti maggiori .
cronvel

84

Uno può usare .toFixed(NumberOfDecimalPlaces).

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

4
Anche perché aggiunge zeri finali, che non è quello che la domanda originale ha posto .
Alastair Maw,

2
Ma gli zeri finali sono facilmente rimossi con una regex, cioè `Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Ciad

1
@daniel la risposta migliore è la stessa (per ora) ma non gira sempre correttamente, prova a +(1.005).toFixed(2)restituire 1invece di 1.01.
Emile Bergeron,

3
@ChadMcElligott: Il tuo regex non funziona bene con numeri interi: Number(9).toFixed(2).replace(/0+$/, '')=> "9."
Jacob van Lingen,

Sembra che questo non tonda. Fondamentalmente si tronca. In alcuni casi, abbastanza bene! Grazie.
iedmrc,

78

Questa domanda è complicata.

Supponiamo di avere una funzione, roundTo2DP(num)che accetta un float come argomento e restituisce un valore arrotondato al secondo decimale. Cosa dovrebbe valutare ciascuna di queste espressioni?

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

La risposta "ovvia" è che il primo esempio dovrebbe arrotondare a 0,01 (perché è più vicino a 0,01 rispetto a 0,02) mentre gli altri due dovrebbero arrotondare a 0,02 (perché 0,015000000000000000001 è più vicino a 0,02 che a 0,01 e perché 0,015 è esattamente a metà strada tra loro e c'è una convenzione matematica che tali numeri vengono arrotondati per eccesso).

Il trucco, che potresti aver intuito, è che roundTo2DP non è possibile implementarlo per dare quelle risposte ovvie, perché tutti e tre i numeri passati ad esso sono lo stesso numero . I numeri binari in virgola mobile IEEE 754 (il tipo utilizzato da JavaScript) non possono rappresentare esattamente la maggior parte dei numeri non interi, quindi tutti e tre i letterali numerici sopra vengono arrotondati a un numero in virgola mobile valido nelle vicinanze. Questo numero, come succede, è esattamente

0,01499999999999999944488848768742172978818416595458984375

che è più vicino a 0,01 che a 0,02.

Puoi vedere che tutti e tre i numeri sono uguali nella tua console del browser, nella shell dei nodi o in altri interpreti JavaScript. Confrontali e basta:

> 0.014999999999999999 === 0.0150000000000000001
true

Quindi quando scrivo m = 0.0150000000000000001, il valore esatto dim quello che finisco è più vicino di 0.01quanto non lo sia 0.02. Eppure, se mi converto min una stringa ...

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

... ottengo 0,015, che dovrebbe rotondo a 0,02, e che è notevolmente non il numero 56 decimale-posto che in precedenza detto che tutti questi numeri erano esattamente uguale a. Quindi che magia oscura è questa?

La risposta è disponibile nella specifica ECMAScript, nella sezione 7.1.12.1: ToString applicata al tipo Numero . Qui sono stabilite le regole per convertire un numero m in una stringa. La parte chiave è il punto 5, in cui viene generato un intero s le cui cifre verranno utilizzate nella rappresentazione String di m :

lascia che n , k e s siano numeri interi tali che k ≥ 1, 10 k -1s <10 k , il valore Numero per s × 10 n - k è m e k sia il più piccolo possibile. Si noti che k è il numero di cifre nella rappresentazione decimale di s , che s non è divisibile per 10 e che la cifra meno significativa di s non è necessariamente determinata in modo univoco da questi criteri.

La parte fondamentale qui è il requisito che " k è il più piccolo possibile". Ciò che equivale a tale requisito è un requisito che, dato un Numero m, il valore di String(m)deve avere il minor numero possibile di cifre pur soddisfacendo il requisito che Number(String(m)) === m. Dato che lo sappiamo già 0.015 === 0.0150000000000000001, ora è chiaro perché String(0.0150000000000000001) === '0.015'deve essere vero.

Naturalmente, nessuna di queste discussioni ha risposto direttamente a ciò che roundTo2DP(m) dovrebbe tornare. Se mil valore esatto è 0,01499999999999999944488848768742172978818416595458984375, ma la sua rappresentazione String è '0,015', allora qual è la risposta corretta - matematicamente, praticamente, filosoficamente o qualsiasi altra cosa - quando la arrotondiamo a due decimali?

Non esiste un'unica risposta corretta a questo. Dipende dal tuo caso d'uso. Probabilmente vuoi rispettare la rappresentazione String e arrotondare verso l'alto quando:

  • Il valore rappresentato è intrinsecamente discreto, ad esempio una quantità di valuta in una valuta di 3 decimali come i dinari. In questo caso, il valore vero di un numero come 0,015 è 0,015 e la rappresentazione 0,0149999999 ... che ottiene in virgola mobile binaria è un errore di arrotondamento. (Naturalmente, molti sosterranno, ragionevolmente, che dovresti usare una libreria decimale per gestire tali valori e non rappresentarli mai come numeri binari in virgola mobile in primo luogo.)
  • Il valore è stato digitato da un utente. In questo caso, ancora una volta, il numero decimale esatto inserito è più "vero" rispetto alla rappresentazione binaria in virgola mobile più vicina.

D'altra parte, probabilmente vuoi rispettare il valore binario in virgola mobile e arrotondare verso il basso quando il tuo valore proviene da una scala intrinsecamente continua, ad esempio se si tratta di una lettura da un sensore.

Questi due approcci richiedono un codice diverso. Per rispettare la rappresentazione String del Numero, possiamo (con un po 'di codice ragionevolmente sottile) implementare il nostro arrotondamento che agisce direttamente sulla rappresentazione String, cifra per cifra, usando lo stesso algoritmo che avresti usato a scuola quando è stato insegnato come arrotondare i numeri. Di seguito è riportato un esempio che rispetta il requisito del PO di rappresentare il numero con 2 decimali "solo quando necessario" rimuovendo gli zero finali dopo il punto decimale; potresti ovviamente aver bisogno di modificarlo in base alle tue precise esigenze.

/**
 * 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+$/, '')
}

Esempio di utilizzo:

> 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'

La funzione sopra è probabilmente ciò che si desidera utilizzare per evitare che gli utenti che assistano ai numeri che hanno inserito siano arrotondati in modo errato.

(In alternativa, puoi anche provare la libreria round10 che fornisce una funzione simile con un'implementazione completamente diversa.)

E se avessi il secondo tipo di Numero - un valore preso da una scala continua, dove non c'è motivo di pensare che le rappresentazioni decimali approssimative con meno decimali siano più accurate di quelle con più? In tal caso, non vogliamo rispettare la rappresentazione String, poiché quella rappresentazione (come spiegato nelle specifiche) è già un po 'arrotondata; non vogliamo fare l'errore di dire "0,014999999 ... 375 arrotondamenti fino a 0,015, che arrotondano fino a 0,02, quindi 0,014999999 ... 375 arrotondamenti fino a 0,02".

Qui possiamo semplicemente usare il toFixedmetodo integrato . Nota che chiamando Number()la stringa restituita da toFixed, otteniamo un numero la cui rappresentazione di stringa non ha zero finali (grazie al modo in cui JavaScript calcola la rappresentazione di stringa di un numero, discusso in precedenza in questa risposta).

/**
 * 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);
}

Questo non funziona in alcuni casi limite: provare ( jsfiddle ) con roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Risultato atteso (come in PHP echo round(362.42499999999995, 2)): 362.43. Risultato effettivo:362.42
Dr. Gianluigi Zane Zanettini,

1
@ Dr.GianluigiZaneZanettini Huh. Strano. Non sono sicuro del motivo per cui PHP roundfornisce 362.43. Ciò sembra intuitivamente sbagliato, dal momento che 362.42499999999995 è inferiore a 362.425 (in matematica e in codice - 362.42499999999995 < 362.425è vero sia in JS che in PHP). Né la risposta di PHP minimizza la distanza tra i numeri in virgola mobile originali e arrotondati, da allora 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. Secondo php.net/manual/en/function.round.php , i PHP roundseguono lo standard C99; Dovrò avventurarmi nella terra di C per capire cosa sta succedendo.
Mark Amery,

1
Dopo aver esaminato l'implementazione dell'arrotondamento nella fonte di PHP , non ho idea di cosa stia facendo. È un'implementazione orribilmente complicata piena di macro e rami e conversioni di stringhe e ramificazione su valori di precisione magica codificati. Non sono sicuro di cosa dire oltre "la risposta di PHP è palesemente sbagliata e dovremmo presentare una segnalazione di bug". A proposito, come hai trovato il numero 362.42499999999995?
Mark Amery,

1
@ Dr.GianluigiZaneZanettini Ho creato una segnalazione di bug: bugs.php.net/bug.php?id=75644
Mark Amery il

2
@ Dr.GianluigiZaneZanettini "Ho un senso?" - no; non è così che funziona l'arrotondamento. 1.000005 termina in cinque, ma se arrotondato al numero intero più vicino la risposta dovrebbe essere 1, non 2. Allo stesso modo, 1499995 termina in cinque, ma se arrotondato al milione più vicino, il risultato dovrebbe essere 1000000, non 2000000. Nel caso di 362.4249999999999995 essendo arrotondato a 2 DP, ciò che dovrebbe determinare la direzione di arrotondamento è il terzo decimale, che è 4.
Mark Amery

76

Considerare .toFixed()e .toPrecision():

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


1
toFixed aggiunge i punti decimali a ogni valore, qualunque cosa accada.
puzzolente

13
Entrambi sono inutili qui
Esailija,

Sfortunatamente, entrambe le funzioni aggiungeranno ulteriori cifre decimali che @stinkycheeseman sembra non voler.
Jackwanders il


2
restituiscono stringhe e non numeri, quindi formattano e non calcolano ..
saimiris_devel

63

Un metodo di arrotondamento preciso. Fonte: 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);
        };
    }
})();

Esempi:

// 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

Qualcuno l'ha inserito su GitHub e anche su npm: github.com/jhohlfeld/round10
Jo Liss,

Nah, Math.round10(3544.5249, -2)restituisce 3544,52 invece 3544,53
Matija,

2
@Matija Wolfram alpha dice anche 3544.52. Si desidera ridurre al minimo l'errore tra il numero corrente e l'approssimazione arrotondata. L'approssimazione più vicina di 3544.52492 decimali è 3544.52(errore = 0,0049). Se lo fosse 3544.53, l'errore sarebbe 0,0051. Stai eseguendo arrotondamenti successivi, ad esempio Math.round10 (Math.round10 (3544.5249, -3), -2) che fornisce un errore di arrotondamento più grande e quindi non è desiderabile.
utente

3
@Matija, dal punto di vista matematico, 3544.5249 arrotondato è 3544.52, non 3544.53, quindi questo codice è corretto. Se vuoi che sia arrotondato a 3544.53 in questo e in casi come questo (anche difficile è errato), fai qualcosa del genere:number += 0.00011
Bozidar Sikanjic,

@Matija: penso che la funzione funzioni come dovrebbe. Forse vuoi ripetere l'arrotondamento come:Math.round10( Math.round10(3544.5249, -3) , -2)
jumxozizi il

60

Nessuna delle risposte trovate qui è corretta . @stinkycheeseman ha chiesto di arrotondare per eccesso , tutti hanno arrotondato il numero.

Per arrotondare, utilizzare questo:

Math.ceil(num * 100)/100;

15
L'input e l'output di esempio mostrano che sebbene la domanda dicesse "arrotondare per eccesso ...", in realtà doveva essere "arrotondata per ...".
JayDM,

2
@stinkycheeseman stava segnalando un errore in un caso specifico, non voleva arrotondare sempre come ceil, voleva solo 0,005 arrotondare a 0,01
mjaggard

9
È stato riscontrato un bug strano durante il test: Math.ceil(1.1 * 100)/100;ritorna 1.11, perché 1.1 * 100 è 110.00000000000001secondo i moderni browser Firefox, Chrome, Safari e Opera ... IE, alla vecchia maniera, continua a pensare 1.1*100=1100.
skobaljic,

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

1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100tornerà anche 1.11in Firefox, il problema / bug dei browser moderni è moltiplicato e la gente dovrebbe saperlo (ad esempio ho lavorato su un gioco della lotteria).
skobaljic,

47

Ecco un modo semplice per farlo:

Math.round(value * 100) / 100

Potresti voler andare avanti e creare una funzione separata per farlo per te:

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

Quindi passeresti semplicemente il valore.

È possibile migliorarlo per arrotondare a qualsiasi numero arbitrario di decimali aggiungendo un secondo parametro.

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

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

2
Questa soluzione è sbagliata, vedi stackoverflow.com/questions/38322372/... se inserite 156.893,145 e rotondo con funzione di cui sopra si ottiene 156.893,14 invece di 156.893,15 !!!
saimiris_devel,

2
@saimiris_devel, sei riuscito a trovare un'istanza in cui la rappresentazione numerica dei numeri in JavaScript non riesce. È corretto che si arrotonda in modo errato. Ma questo è perché il numero del campione moltiplicato per 100 è già rotto (15689314.499999998). In realtà, una risposta completa richiederebbe una libreria che è stata specificamente sviluppata per tenere conto delle incongruenze nella gestione dei numeri reali da parte di JavaScript. Altrimenti, probabilmente potresti invalidare una qualsiasi delle risposte che sono state fornite a questa domanda.
JayDM,

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

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

1
Questo non darà sempre gli stessi risultati che otterresti se prendessi la rappresentazione String del tuo Numero e lo arrotondassi. Ad esempio +(0.015).toFixed(2) == 0.01,.
Mark Amery,

35

Per me Math.round () non stava dando una risposta corretta. Ho scoperto che Fixed (2) funziona meglio. Di seguito sono riportati esempi di entrambi:

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

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


È importante notare che toFixed non esegue un arrotondamento e che Math.round arrotonda solo al numero intero più vicino. Per preservare i decimali dobbiamo quindi moltiplicare il numero originale per il numero di poteri di dieci i cui zeri rappresentano il numero desiderato di decimali, quindi dividere il risultato per lo stesso numero. Nel tuo caso: Math.round (43000/80000 * 100 * 100) / 100. Finalmente si può applicare Fisso (2) per garantire che ci siano sempre due decimali nel risultato (con zeri finali ove necessario) - perfetto per allineare a destra una serie di numeri presentati verticalmente :)
Turbo

7
È anche importante notare che .toFIxed () genera una stringa, non un numero.
carpiediem,

2
Questo non risolve ancora il problema 1.005. (1.005).toFixed(2)risulta ancora a 1.00.
DPac,

34

Usa questa funzione Number(x).toFixed(2);


8
Avvolgi di Numbernuovo tutto, se non vuoi che venga restituito come stringa:Number(Number(x).toFixed(2));

5
La Numberchiamata non è necessaria, x.toFixed(2)funziona.
bgusach,

3
@bgusach Chiamata numerica necessaria, poiché l'istruzione x.toFixed (2) restituisce una stringa e non un numero. Per convertire nuovamente in numero, dobbiamo racchiudere il numero
Mohan Ram

2
Quando si utilizza questo metodo , viene (1).toFixed(2)restituito 1.00, ma 1in questo caso è necessario l'interrogatore .
Eugene Mala

1
Questo non funziona, 1.005.toFixed(2)cede "1"quando dovrebbe essere "1.01".
Adam Jagosz,

33

2017
Basta usare il codice nativo.toFixed()

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

Se devi essere severo e aggiungere cifre solo se necessario, puoi usarlo replace

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

3
Il metodo toFixed restituisce una stringa. Se si desidera un risultato numerico, è necessario inviare il risultato di toFixed a parseFloat.
Zambonilli,

@Zambonilli O semplicemente moltiplicare per 1 se necessario. ma poiché il numero fisso la maggior parte dei casi sono per la visualizzazione e non per la stringa di calcolo è il formato giusto
pery mimon

2
-1; non solo è stato toFixedsuggerito da risposte multiple anni prima delle vostre, ma non soddisfa la condizione "solo se necessario" nella domanda; (1).toFixed(2)"1.00"dove l'interlocutore desiderato "1".
Mark Amery,

Ok capito. Aggiungo qualche soluzione anche per quel caso
pery mimon

Se stai usando lodash, è ancora più semplice: _.round (number, decimalPlace) Eliminato il mio ultimo commento, perché ha un problema. Lodash _.round FUNZIONA, però. 1.005 con il decimale di 2 converte in 1,01.
Devin Fields,

32

Prova questa soluzione leggera :

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

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

Qualcuno sa se c'è qualche differenza tra questo e return Number(x.toFixed(digits))?

1
@JoeRocc ... non dovrebbe fare alcuna differenza per quanto posso vedere dal momento che .toFixed()consente solo solo per i numeri.
petermeissner,

4
Questa risposta presenta lo stesso problema menzionato più volte in questa pagina. Prova a round(1.005, 2)vedere un risultato 1invece di 1.01.
MilConDoin il

sembra più un problema dell'arrotondamento? - ce ne sono più di quanto si possa immaginare: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner,

31

Ci sono un paio di modi per farlo. Per le persone come me, la variante di 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))
}

Uso:

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

Se il tuo progetto utilizza jQuery o lodash, puoi trovare anche quello giusto round metodo nelle librerie.

Aggiornamento 1

Ho rimosso la variante n.toFixed(2), perché non è corretta. Grazie @ valanga1


La seconda opzione restituirà una stringa con esattamente due punti decimali. La domanda richiede punti decimali solo se necessario. La prima opzione è migliore in questo caso.
Marcos Lima,

@MarcosLima Number.toFixed()restituirà una stringa ma prima con un simbolo più, l'interprete JS convertirà la stringa in un numero. Questo è uno zucchero sintattico.
Stanleyxu2005,

Su Firefox, alert((+1234).toFixed(2))mostra "1234.00".
Marcos Lima,

Su Firefox, alert(+1234.toFixed(2))genera SyntaxError: identifier starts immediately after numeric literal. Mi attengo alla prima opzione.
Marcos Lima,

Questo non funziona in alcuni casi limite: provare ( jsfiddle ) con 362.42499999999995. Risultato atteso (come in PHP echo round(362.42499999999995, 2)): 362.43. Risultato effettivo:362.42
Dr. Gianluigi Zane Zanettini,

26

Se stai usando la libreria lodash, puoi usare il metodo round di lodash come segue.

_.round(number, precision)

Per esempio:

_.round(1.7777777, 2) = 1.78

@Peter L'insieme di funzionalità fornite da Lodash è davvero buono rispetto allo standard Javascript. Tuttavia, ho sentito che Lodash ha qualche problema di prestazioni rispetto al JS standard. codeburst.io/…
Madura Pradeep,

1
Accetto il tuo punto che ci sono degli svantaggi prestazionali nell'uso di lodash. Penso che questi problemi siano comuni a molte astrazioni. Ma guarda quante risposte ci sono su questo thread e come le soluzioni intuitive falliscono per i casi limite. Abbiamo visto questo modello con jQuery e il problema di root è stato risolto quando i browser hanno adottato uno standard comune che ha risolto la maggior parte dei nostri casi d'uso. I colli di bottiglia delle prestazioni sono stati quindi spostati nei motori del browser. Penso che lo stesso dovrebbe succedere. :)
Peter,

26

Da ES6 esiste un modo "corretto" (senza ignorare la statica e creare soluzioni alternative) per farlo utilizzando 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));

allora puoi solo parseFloate gli zero "spariranno".

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

Tuttavia non risolve il "problema di arrotondamento di 1.005", poiché è intrinseco al modo in cui vengono elaborate le frazioni float .

console.log(1.005 - 0.005);

Se sei aperto alle librerie puoi usare 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)ritorna ancora 1.00invece che 1.01effettivamente.
Giacomo,

toPrecisionrestituisce una stringa che modifica il tipo di output desiderato.
adamduren,

@Giacomo Non è un difetto del .toPrecisionmetodo, è una specificità dei numeri in virgola mobile (quali sono i numeri in JS) - prova 1.005 - 0.005, tornerà 0.9999999999999999.
shau-kote,

1
(1).toPrecision(3)restituisce '1.00', ma 1in questo caso l' interrogante voleva avere .
Eugene Mala

1
Come ha detto @Giacomo, questa risposta sembra confondere "cifre significative" con "arrotondamento a un numero di cifre decimali". toPrecisionfa il formato, non il secondo, e non è una risposta alla domanda del PO, anche se all'inizio può sembrare rilevante, si sbaglia molto. Vedi en.wikipedia.org/wiki/Significant_figures . Ad esempio Number(123.4).toPrecision(2)restituisce "1.2e+2"e Number(12.345).toPrecision(2)restituisce "12". Concordo anche con il punto di @adamduren che restituisce una stringa che non è desiderabile (non è un grosso problema ma non è desiderabile).
Neek,

23

MarkG e Lavamantis hanno offerto una soluzione molto migliore di quella che è stata accettata. È un peccato che non ottengano più voti!

Ecco la funzione che utilizzo per risolvere i problemi di decimali in virgola mobile anche basati su MDN . È ancora più generico (ma meno conciso) della soluzione di 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));
}

Usalo con:

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

Rispetto alla soluzione di Lavamantis, possiamo fare ...

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

2
La tua soluzione non copre alcuni casi rispetto alla soluzione MDN. Anche se può essere più breve, non è preciso ...
astorije,

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


18

L'approccio più semplice sarebbe usare toFixed e quindi rimuovere gli zeri finali usando la funzione Number:

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

questo non funziona in tutti i casi. fare test approfonditi prima di pubblicare le risposte.
baburao,

@baburao Si prega di inviare un caso in cui la soluzione sopra non funziona
Marcin Wanago,

numero const = 15; Numero (Number.toFixed (2)); // 15,00 anziché 15
Kevin Jhangiani il

1
@KevinJhangiani const number = 15; Numero (Number.toFixed (2)); // 15 - L'ho provato su Chrome e Firefox più recenti
Marcin Wanago il

@KevinJhangiani come si ottiene 15.00? I numeri in JS no memorizzano le posizioni decimali e qualsiasi display tronca automaticamente le posizioni decimali in eccesso (eventuali zero alla fine).
VLAZ,

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

toFixed(2) qui 2 è il numero di cifre fino a cui vogliamo arrotondare questo num.


questo .toFixed () è più semplice da implementare. basta attraversarlo una volta.
Ritesh Dhuri,


14

Il modo più semplice:

+num.toFixed(2)

Lo converte in una stringa e quindi di nuovo in un numero intero / float.


Grazie per questa risposta più semplice. Tuttavia, cos'è "+" in + num? Non ha funzionato per me dove il valore decimale è entrato in stringa. Ho fatto: (num * 1) .toFixed (2).
Ethan,

@momo basta cambiare l'argomento toFixed()in 3. Quindi sarebbe +num.toFixed(3).
Funziona

1
@Edmund Dovrebbe restituire 1,01, non 1,00
mmm

13

Ecco un metodo prototipo:

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

Usa qualcosa del genere "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
non se l'imprecisione è intrinseca alla rappresentazione float. lo rimuoveresti e poi reintrodurresti lo stesso errore convertendolo di nuovo in float!
Ben McIntyre,

12

Un modo per ottenere tale arrotondamento solo se necessario è utilizzare Number.prototype.toLocaleString () :

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

Ciò fornirà esattamente l'output previsto, ma come stringhe. Puoi comunque riconvertirli in numeri se non è il tipo di dati che ti aspetti.


Questa è la soluzione più pulita che esiste di gran lunga e elude tutti i complicati problemi in virgola mobile, ma per il supporto MDN è ancora incompleto - Safari non supporta ancora il passaggio di argomenti toLocaleString.
Mark Amery,

@MarkAmery Per ora, solo il browser Android ha alcuni problemi: caniuse.com/#search=toLocaleString
ptyskju

12

Dopo aver esaminato varie iterazioni di tutti i modi possibili per ottenere una precisione di arrotondamento decimale veramente accurata, è chiaro che la soluzione più accurata ed efficiente è utilizzare Number.EPSILON. Ciò fornisce una vera soluzione matematica al problema della precisione matematica in virgola mobile. Può essere facilmente polifillato come mostrato qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON per supportare tutti gli ultimi utenti IE rimanenti (quindi di nuovo forse noi dovrebbe smettere di farlo).

Adattato dalla soluzione fornita qui: https://stackoverflow.com/a/48850944/6910392

Una soluzione semplice che fornisce arrotondamenti decimali accurati, pavimentazione e soffitto, con una variabile di precisione opzionale senza aggiungere un'intera libreria.

AGGIORNAMENTO: Come ha notato Sergey nei commenti, c'è una limitazione a questo (o qualsiasi) metodo che vale la pena sottolineare. Nel caso di numeri come 0.014999999999999999, si verificheranno comunque inesattezze che sono il risultato del superamento del limite assoluto delle limitazioni di precisione per la memorizzazione di valori in virgola mobile. Non esiste alcuna matematica o altra soluzione che possa essere applicata per tener conto di ciò, poiché il valore stesso viene immediatamente valutato come 0,015. Puoi confermarlo semplicemente invocando quel valore da solo nella console. A causa di questa limitazione, non sarebbe nemmeno possibile utilizzare la manipolazione di stringhe per ridurre questo valore, poiché la sua rappresentazione di stringa è semplicemente "0,015". Qualsiasi soluzione per tener conto di ciò dovrebbe essere applicata logicamente alla fonte dei dati prima di accettare il valore in uno script,

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)) // restituisce 0.02
Sergey

Buona pesca! Il problema è con l'archiviazione in virgola mobile in JS, ci saranno sempre alcuni casi limite. La buona notizia è che la matematica che applichi a Number.EPSILON per prima può essere regolata con maggiore precisione per spingere ulteriormente i casi limite al limite. Se non vuoi garantire alcuna possibilità per casi limite, la tua unica vera soluzione sarà la manipolazione delle stringhe e quindi la matematica. Nel momento in cui esegui qualsiasi calcolo matematico sul valore (anche nel tentativo di spostare il decimale), hai già prodotto il bug.
KFish

In realtà, a un'ulteriore ispezione, ciò non è dovuto a nessuna delle matematiche coinvolte, ma piuttosto il problema si manifesta immediatamente invocando il valore specificato. Puoi confermarlo semplicemente digitando quel numero nella console e vedere che viene immediatamente valutato a 0,015. Pertanto, ciò rappresenterebbe il limite assoluto di precisione per qualsiasi numero in virgola mobile in JS. In questo caso non puoi nemmeno convertirlo in stringa e manipolarlo poiché il valore della stringa sarebbe "0,015"
KFish

11

Questa è la soluzione più semplice ed elegante (e io sono la migliore del mondo;):

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
È un bel modo di riscrivere la risposta accettata per accettare un argomento usando la Enotazione.
AxelH,

1
Questo non funziona in alcuni casi limite: provare ( jsfiddle ) roundToX(362.42499999999995, 2). Risultato atteso (come in PHP echo round(362.42499999999995, 2)): 362.43. Risultato effettivo:362.42
Dr. Gianluigi Zane Zanettini,

6
IMHO, il tuo risultato PHP è sbagliato. Qualunque cosa accada dopo il terzo decimale, se il terzo decimale è inferiore a 5, il secondo decimale dovrebbe rimanere lo stesso. Questa è la definizione matematica.
Soldeplata Saketos,

1
Per essere ancora più conciso, "e +" può essere semplicemente "e".
Lonnie Best
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.