Arrondir à au plus 2 décimales (uniquement si nécessaire)

2760

Je voudrais arrondir au plus 2 décimales, mais seulement si nécessaire .

Contribution:

10
1.7777777
9.1

Production:

10
1.78
9.1

Comment faire cela en JavaScript?

stinkycheeseman
la source
22
J'ai fait un violon avec de nombreuses techniques proposées comme solutions ici ... afin que vous puissiez comparer: violon
dsdsdsdsd
37
Personne ne semble en être conscient Number.EPSILON. Utilisez Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel
3
Pour les nouveaux lecteurs, vous ne pouvez le faire que si vous avez un résultat de type chaîne . Les calculs à virgule flottante sur les représentations internes binaires des nombres signifient qu'il y a toujours des nombres qui ne peuvent pas être représentés comme des décimales nettes.
Walf
9
@cronvel Pouvez-vous expliquer la raison de l'utilisation Number.EPSILONici?
Bruce Sun
5
Je suis tombé dans le terrier du lapin et j'ai testé certaines des réponses les plus intéressantes de cette page (première page uniquement). Voici un Codepen . Astuce: plus la réponse a de votes positifs, moins il y a de chances qu'elle fonctionne correctement.
Adam Jagosz

Réponses:

3497

Utilisation Math.round(num * 100) / 100

Edit: pour garantir que des choses comme 1,005 arrondissent correctement, nous utilisons

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

Brian Ustas
la source
395
Bien que cela fonctionnera pour la plupart des cas, cela ne fonctionnera pas pour 1.005 qui finira par être 1 au lieu de 1.01
James
83
@James Wow c'est vraiment bizarre - je travaille dans la console de développement Chrome et je remarque que 1.005 * 100 = 100.49999999999999. Math.round (100.4999999999999999) est évalué à 100, tandis que Math.round (100.5) est évalué à 101. IE9 fait la même chose. Cela est dû à l' étrangeté en virgule flottante en javascript
stinkycheeseman
153
Une solution simple. Pour 2 dp, utilisez Math.round((num + 0.00001) * 100) / 100. Essayez Math.round((1.005 + 0.00001) * 100) / 100etMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan
31
@mrkschan Pourquoi cela fonctionne-t-il et est-ce infaillible pour tous les chiffres?
CMCDragonkai
86
Pour ceux d'entre vous qui ne comprennent pas, cette technique est appelée mise à l'échelle. Fondamentalement, ce que la réponse fait ici est d'amener deux chiffres à travers la virgule décimale en transformant le chiffre en un nombre entier pour éviter tous les problèmes de virgule flottante fous, arrondissez-le puis retransformez-le en ce qu'il était auparavant en divisant par 100 et vous avez votre réponse à 2dp.
Alex_Nabu
3064

Si la valeur est un type de texte:

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

Si la valeur est un nombre:

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

Il y a un inconvénient: des valeurs comme 1,5 donneront "1,50" en sortie. Un correctif suggéré par @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.

Il semble que ce Math.roundsoit une meilleure solution. Mais ce n'est pas! Dans certains cas, il ne sera PAS arrondi correctement:

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

toFixed () ne sera PAS non plus arrondi correctement dans certains cas (testé dans Chrome v.55.0.2883.87)!

Exemples:

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.

Je suppose que c'est parce que 1.555 est en fait quelque chose comme flotteur 1.55499994 dans les coulisses.

La solution 1 consiste à utiliser un script avec l'algorithme d'arrondi requis, par exemple:

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

REMARQUE: ce n'est pas une solution universelle pour tout le monde. Il existe plusieurs algorithmes d'arrondi différents, votre implémentation peut être différente, dépend de vos besoins. https://en.wikipedia.org/wiki/Rounding

La solution 2 consiste à éviter les calculs frontaux et à extraire les valeurs arrondies du serveur principal.

A Kunin
la source
81
Cette approche (toFixed) est bonne et a fonctionné pour moi, mais elle ne répond pas spécifiquement à la demande initiale de "uniquement lorsque cela est nécessaire". (Il arrondit de 1,5 à 1,50, ce qui casse les spécifications.)
Par Lundberg le
29
Pour l'exigence «lorsque cela est nécessaire», procédez comme parseFloat(number.toFixed(decimalPlaces)); suit : @PerLundberg
Onur Yıldırım
36
parseFloat("55.555").toFixed(2)revient "55.55"dans la console de développement Chrome.
Levi Botelho
22
Il n'y a aucun avantage à utiliser toFixed au lieu de Math.round; toFixed conduit aux mêmes problèmes d'arrondi (essayez-les avec 5.555 et 1.005), mais est comme 500x (sans blague) plus lent que Math.round ... On dirait que la réponse @MarkG est la plus précise ici.
Pierre
17
toFixed ne renvoie pas «parfois» une chaîne, il renvoie toujours une chaîne.
McGuireV10
464

Vous pouvez utiliser

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

J'ai trouvé ça sur MDN . Leur façon d'éviter le problème avec 1.005 qui a été mentionné .

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57
MarkG
la source
13
@Redsandro, +(val)est l'équivalent coercitif de l'utilisation Number(val). La concaténation de "e-2" à un nombre a entraîné une chaîne qui devait être reconvertie en nombre.
Jack
41
Méfiez-vous de cela pour les flotteurs petits et grands qui produiraient du NaN car, par exemple + "1e-21 + 2" ne sera pas analysé correctement.
Pierre
16
Vous avez `` résolu '' le `` problème '' 1.005, mais en avez introduit un nouveau: maintenant, dans la console Chrome, roundToTwo(1.0049999999999999)sort en 1.01 (inévitablement, depuis 1.0049999999999999 == 1.005). Il me semble que le flottant que vous obtenez si vous tapez num = 1.005«évidemment» «devrait» arrondir à 1,00, car la valeur exacte de num est inférieure à 1,005. Bien sûr, il me semble également que la chaîne "1.005" "évidemment" "devrait" être arrondie à 1.01. Le fait que différentes personnes semblent avoir des intuitions différentes sur le comportement correct réel ici explique en partie pourquoi c'est compliqué.
Mark Amery
35
Il n'y a pas de nombre (virgule flottante) entre les deux 1.0049999999999999et 1.005, par définition, ils sont donc le même nombre. C'est ce qu'on appelle une coupure de dédoublement.
Azmisov
6
@Azmisov a raison. Alors que 1.00499 < 1.005est true, 1.0049999999999999 < 1.005évalue à false.
falconepl
146

La réponse de MarkG est la bonne. Voici une extension générique pour un nombre quelconque de décimales.

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

Usage:

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

Test de l'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');
  });
})
Lavamantis
la source
20
Pierre a soulevé un sérieux problème avec la réponse de MarkG.
dsjoerg
9
Remarque: Si vous ne voulez pas modifier le nombre.prototype - écrivez simplement ceci comme une fonction: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman
2
Une façon cool de développer cela. Vous pouvez vérifier si la décimale est négative, puis inverser e + et e-. Alors n = 115; n.round (-2); produirait 100
Lee Louviere
3
Essayez n: 1e + 19 - il renvoie NaN
DavidJ
3
Cet algorithme arrondit toujours (au lieu de s'éloigner de zéro). Donc, en cas de nombres négatifs, la sortie n'est pas ce à quoi vous pourriez vous attendre:(-1.005).round(2) === -1
Aleksej Komarov
124

Tu devrais utiliser:

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

Personne ne semble en être conscient Number.EPSILON.

Il convient également de noter que ce n'est pas une bizarrerie JavaScript comme certaines personnes l'ont déclaré.

C'est simplement la façon dont les nombres à virgule flottante fonctionnent dans un ordinateur. Comme 99% des langages de programmation, JavaScript n'a pas de nombres à virgule flottante faits maison ; il s'appuie sur le CPU / FPU pour cela. Un ordinateur utilise le binaire, et en binaire, il n'y a pas de nombres comme0.1 , mais une simple approximation binaire pour cela. Pourquoi? Pour la même raison que 1/3 ne peut pas être écrit en décimal: sa valeur est 0,33333333 ... avec une infinité de trois.

Voici venir Number.EPSILON. Ce nombre est la différence entre 1 et le nombre suivant existant dans les nombres à virgule flottante double précision. C'est tout: il n'y a pas de nombre entre 1et 1 + Number.EPSILON.

ÉDITER:

Comme demandé dans les commentaires, clarifions une chose: ajouter Number.EPSILON n'est pertinent que lorsque la valeur à arrondir est le résultat d'une opération arithmétique, car elle peut avaler un delta d'erreur en virgule flottante.

Ce n'est pas utile lorsque la valeur provient d'une source directe (par exemple: littéral, entrée utilisateur ou capteur).

EDIT (2019):

Comme @maganap et certains peuples l'ont souligné, il est préférable d'ajouter Number.EPSILONavant de multiplier:

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

EDIT (décembre 2019):

Dernièrement, j'utilise une fonction similaire à celle-ci pour comparer les nombres compatibles avec 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 ;
}

Mon cas d'utilisation est une bibliothèque d'assertion + validation de données que je développe depuis de nombreuses années.

En fait, dans le code que j'utilise ESPILON_RATE = 1 + 4 * Number.EPSILONet EPSILON_ZERO = 4 * Number.MIN_VALUE(quatre fois le epsilon), parce que je veux un vérificateur d'égalité suffisamment lâche pour cumuler les erreurs en virgule flottante.

Jusqu'à présent, cela me semble parfait. J'espère que cela aidera.

cronvel
la source
1
@palota, vous pouvez définir un nombre de dents vraiment comme le cronvel EPSILON le mentionne
Daniel San
3
C'est la bonne réponse! Le problème lui-même est lié à la façon dont les nombres flottants fonctionnent en interne, et non au javascript
Daniel San
22
En fait, vous devez ajouter epsilon AVANT de multiplier par 100 Math.round( (num + Number.EPSILON) * 100) / 100.. Je suis d'accord également que c'est la bonne méthode pour arrondir correctement (bien que ce ne soit pas exactement ce qui a été demandé dans cette question).
maganap
2
@cronvel Hmm. Tu as raison; le choix de la direction ne du sens. Donc je suppose que mon objection restante est juste que faire cela n'a de sens que si vous travaillez dans un domaine où vous avez une raison de principe de penser que votre valeur est un "chiffre rond". Si vous savez que votre entrée est le résultat d'opérations arithmétiques simples sur des nombres avec un petit nombre de décimales, alors bien sûr, vous n'avez probablement que 0.004999999999999999le résultat d'une erreur à virgule flottante composée et le résultat mathématiquement correct était probablement 0,005. Si c'est une lecture d'un capteur? Pas tellement.
Mark Amery
1
@marchaos Il ne manque pas: moitié sont toujours arrondis en place . Par exemple Math.round(1.5)= 2, mais Math.round(-1.5)= -1. C'est donc parfaitement cohérent. Ici, -1 est supérieur à -2, tout comme -1000 est supérieur à -1000.01. À ne pas confondre avec de plus grands nombres absolus .
cronvel
84

On peut utiliser .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Gourav Singla
la source
4
Aussi parce qu'il ajoute des zéros de fin, ce qui n'est pas ce que la question d'origine demandait .
Alastair Maw
2
Mais les zéros de fin sont facilement supprimés avec une expression régulière, c'est-à-dire `Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Chad
1
@daniel la première réponse est la même (à partir de maintenant) mais elle ne tourne pas toujours correctement, essayez +(1.005).toFixed(2)qui retourne 1au lieu de 1.01.
Emile Bergeron
3
@ChadMcElligott: Votre expression régulière ne fonctionne pas bien avec les entiers: Number(9).toFixed(2).replace(/0+$/, '')=> "9."
Jacob van Lingen
Il semble que cela ne tourne pas rond. Il est simplement tronqué. Dans certains cas, assez bien! Merci.
iedmrc
78

Cette question est compliquée.

Supposons que nous ayons une fonction roundTo2DP(num),, qui prend un flottant comme argument et renvoie une valeur arrondie à 2 décimales. Que doit évaluer chacune de ces expressions?

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

La réponse `` évidente '' est que le premier exemple devrait arrondir à 0,01 (car il est plus proche de 0,01 que de 0,02) tandis que les deux autres devraient arrondir à 0,02 (car 0,0150000000000000001 est plus proche de 0,02 que de 0,01, et parce que 0,015 est exactement à mi-chemin entre et il existe une convention mathématique selon laquelle ces nombres sont arrondis).

Le problème, que vous avez peut-être deviné, est qu'il roundTo2DP ne peut pas être mis en œuvre pour donner ces réponses évidentes, car les trois nombres qui lui sont transmis sont le même nombre . Les nombres à virgule flottante binaires IEEE 754 (le type utilisé par JavaScript) ne peuvent pas représenter exactement la plupart des nombres non entiers, et donc les trois littéraux numériques ci-dessus sont arrondis à un nombre à virgule flottante valide proche. Il se trouve que ce nombre est exactement

0,01499999999999999944488848768742172978818416595458984375

qui est plus proche de 0,01 que de 0,02.

Vous pouvez voir que les trois nombres sont les mêmes sur la console de votre navigateur, le shell Node ou tout autre interpréteur JavaScript. Il suffit de les comparer:

> 0.014999999999999999 === 0.0150000000000000001
true

Donc, quand j'écris m = 0.0150000000000000001, la valeur exacte de cem que je me retrouve est plus proche 0.01qu'elle ne l'est 0.02. Et pourtant, si je me convertis men une chaîne ...

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

... J'obtiens 0,015, qui devrait arrondir à 0,02, et qui n'est visiblement pas le nombre à 56 décimales. J'ai dit plus tôt que tous ces nombres étaient exactement égaux. Alors, quelle magie noire est-ce?

La réponse peut être trouvée dans la spécification ECMAScript, dans la section 7.1.12.1: ToString appliquée au type Number . Ici, les règles de conversion d'un nombre m en chaîne sont définies. La partie clé est le point 5, dans lequel un entier s est généré dont les chiffres seront utilisés dans la représentation String de m :

soit n , k et s des entiers tels que k ≥ 1, 10 k -1s <10 k , la valeur numérique pour s × 10 n - k est m et k est aussi petit que possible. Notez que k est le nombre de chiffres dans la représentation décimale de s , que s n'est pas divisible par 10 et que le chiffre le moins significatif de s n'est pas nécessairement déterminé de manière unique par ces critères.

L'élément clé ici est l'exigence que " k soit aussi petit que possible". Ce que signifie cette exigence est une exigence selon laquelle, étant donné un nombre m, la valeur de String(m)doit avoir le moins de chiffres possible tout en satisfaisant à l'exigence que Number(String(m)) === m. Puisque nous le savons déjà 0.015 === 0.0150000000000000001, il est maintenant clair pourquoi cela String(0.0150000000000000001) === '0.015'doit être vrai.

Bien sûr, aucune de ces discussions n'a directement répondu à ce qui roundTo2DP(m) devrait revenir. Si mla valeur exacte est 0,01499999999999999944488848768742172978818416595458984375, mais sa représentation de chaîne est '0,015', alors quelle est la bonne réponse - mathématiquement, pratiquement, philosophiquement, ou autre - quand nous l'arrondissons à deux décimales?

Il n'y a pas de réponse correcte unique à cela. Cela dépend de votre cas d'utilisation. Vous voudrez probablement respecter la représentation String et arrondir vers le haut lorsque:

  • La valeur représentée est intrinsèquement discrète, par exemple un montant de devise dans une devise à 3 décimales comme les dinars. Dans ce cas, la vraie valeur d'un nombre comme 0,015 est 0,015 et la représentation 0,0149999999 ... qu'il obtient en virgule flottante binaire est une erreur d'arrondi. (Bien sûr, beaucoup diront, raisonnablement, que vous devez utiliser une bibliothèque décimale pour gérer ces valeurs et ne jamais les représenter comme des nombres à virgule flottante binaire en premier lieu.)
  • La valeur a été saisie par un utilisateur. Dans ce cas, encore une fois, le nombre décimal exact entré est plus «vrai» que la représentation binaire flottante la plus proche.

D'un autre côté, vous voulez probablement respecter la valeur à virgule flottante binaire et arrondir vers le bas lorsque votre valeur provient d'une échelle intrinsèquement continue - par exemple, s'il s'agit d'une lecture provenant d'un capteur.

Ces deux approches nécessitent un code différent. Pour respecter la représentation String du nombre, nous pouvons (avec un peu de code raisonnablement subtil) implémenter notre propre arrondi qui agit directement sur la représentation String, chiffre par chiffre, en utilisant le même algorithme que vous auriez utilisé à l'école lorsque vous ont appris à arrondir les nombres. Voici un exemple qui respecte l'exigence du PO de représenter le nombre à 2 décimales "uniquement lorsque cela est nécessaire" en supprimant les zéros de fin après la virgule; vous devrez peut-être, bien sûr, l'adapter à vos besoins précis.

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

Exemple d'utilisation:

> 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 fonction ci-dessus est probablement celle que vous souhaitez utiliser pour éviter que les utilisateurs ne voient jamais les nombres qu'ils ont entrés être arrondis incorrectement.

(Comme alternative, vous pouvez également essayer la bibliothèque round10 qui fournit une fonction similaire avec une implémentation très différente.)

Mais que se passe-t-il si vous avez le deuxième type de nombre - une valeur prise sur une échelle continue, où il n'y a aucune raison de penser que les représentations décimales approximatives avec moins de décimales sont plus précises que celles avec plus? Dans ce cas, nous ne voulons pas respecter la représentation String, car cette représentation (comme expliqué dans la spécification) est déjà en quelque sorte arrondie; nous ne voulons pas faire l'erreur de dire "0,014999999 ... 375 arrondit à 0,015, ce qui arrondit à 0,02, donc 0,014999999 ... 375 arrondit à 0,02".

Ici, nous pouvons simplement utiliser la toFixedméthode intégrée . Notez qu'en appelant Number()la chaîne renvoyée par toFixed, nous obtenons un nombre dont la représentation de chaîne n'a pas de zéros de fin (grâce à la façon dont JavaScript calcule la représentation de chaîne d'un nombre, discutée plus haut dans cette réponse).

/**
 * 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);
}
Mark Amery
la source
Cela ne fonctionne pas dans certains cas extrêmes : essayez ( jsfiddle ) avec roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Résultat attendu (comme PHP echo round(362.42499999999995, 2)): 362.43. Résultat réel:362.42
Dr.Gianluigi Zane Zanettini
1
@ Dr.GianluigiZaneZanettini Huh. Bizarre. Je ne sais pas pourquoi PHP rounddonne 362.43. Cela semble intuitivement faux, car 362.42499999999995 est inférieur à 362.425 (en mathématiques et en code - 362.42499999999995 < 362.425c'est vrai à la fois en JS et en PHP). Depuis, la réponse de PHP ne minimise pas la distance entre les nombres à virgule flottante d'origine et arrondis 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. Selon php.net/manual/en/function.round.php , PHP roundsuit la norme C99; Je vais devoir m'aventurer au pays de C pour comprendre ce qui se passe.
Mark Amery
1
Après avoir examiné l'implémentation de l'arrondi dans la source PHP , je n'ai aucune idée de ce qu'il fait. C'est une implémentation horriblement compliquée pleine de macros et de branches et de conversions de chaînes et de branchements sur des valeurs de précision magique codées en dur. Je ne sais pas quoi dire au-delà de "la réponse de PHP est manifestement fausse, et nous devrions déposer un rapport de bogue". Comment avez-vous trouvé le numéro 362.42499999999995, au fait?
Mark Amery
1
@ Dr.GianluigiZaneZanettini J'ai créé un rapport de bug: bugs.php.net/bug.php?id=75644
Mark Amery
2
@ Dr.GianluigiZaneZanettini " Ai -je un sens?" - non; ce n'est pas ainsi que l'arrondi fonctionne. 1,000005 se termine par cinq, mais s'il est arrondi à l'entier le plus proche, la réponse doit être 1, et non 2. De même, 1499995 se termine par cinq, mais s'il est arrondi au million le plus proche, le résultat doit être 1000000, et non 2000000. Dans le cas de 362.4249999999999995 étant arrondi à 2 DP, ce qui devrait déterminer la direction d'arrondi est la troisième décimale, qui est 4.
Mark Amery
76

Considérez .toFixed()et .toPrecision():

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

AceCorban
la source
1
toFixed ajoute les décimales à chaque valeur, peu importe quoi.
stinkycheeseman
13
Les deux sont inutiles ici
Esailija
Malheureusement, les deux fonctions ajouteront des décimales supplémentaires que @stinkycheeseman ne semble pas vouloir.
jackwanders
2
ils retournent des chaînes et non des nombres, donc ils formatent et ne calculent pas ..
saimiris_devel
63

Une méthode d'arrondi précise. Source: 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);
        };
    }
})();

Exemples:

// 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
utilisateur
la source
Quelqu'un a également mis cela sur GitHub et npm: github.com/jhohlfeld/round10
Jo Liss
Nah, Math.round10(3544.5249, -2)renvoie 3544.52 au lieu de 3544.53
Matija
2
@Matija Wolfram alpha dit aussi 3544.52. Vous souhaitez minimiser l'erreur entre le nombre actuel et l'approximation arrondie. L'approximation la plus proche de 3544.52492 décimales est 3544.52(erreur = 0,0049). Si c'était le cas 3544.53, l'erreur serait de 0,0051. Vous effectuez des arrondis successifs, c'est-à-dire Math.round10 (Math.round10 (3544.5249, -3), -2), ce qui donne une plus grande erreur d'arrondi et n'est donc pas souhaitable.
utilisateur
3
@Matija, du point de vue mathématique, 3544.5249 arrondi est 3544.52, pas 3544.53, donc ce code est correct. Si vous voulez qu'il soit arrondi à 3544,53 dans ce cas et dans des cas comme celui-ci (même difficile est incorrect), faites quelque chose comme ceci:number += 0.00011
Bozidar Sikanjic
@Matija: Je pense que la fonction fonctionne comme il se doit. Peut-être que vous voulez répéter l'arrondi comme:Math.round10( Math.round10(3544.5249, -3) , -2)
jumxozizi
60

Aucune des réponses trouvées ici n'est correcte . @stinkycheeseman a demandé d' arrondir , vous avez tous arrondi le nombre.

Pour arrondir, utilisez ceci:

Math.ceil(num * 100)/100;
machineaddict
la source
15
L'exemple d'entrée et de sortie montre que bien que la question dise «arrondir ...», elle était en fait destinée à être «arrondie à ...
JayDM
2
@stinkycheeseman signalait une erreur dans un cas spécifique, il ne voulait pas toujours arrondir comme le fait le plafond, il voulait juste que 0,005 arrondisse à 0,01
mjaggard
9
Trouvé un bug bizarre lors des tests Math.ceil(1.1 * 100)/100;- il revient 1.11, car 1.1 * 100 est 110.00000000000001conforme aux tout nouveaux navigateurs modernes Firefox, Chrome, Safari et Opera ... IE, à l'ancienne, pense toujours 1.1*100=1100.
skobaljic
1
@skobaljic tryMath.ceil(num.toFixed(4) * 100) / 100
treeface
1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100reviendra également 1.11dans Firefox, le problème / bug des navigateurs modernes est lié à la multiplication et les gens devraient le savoir (j'ai travaillé sur un jeu de loterie à cette époque par exemple).
skobaljic
47

Voici un moyen simple de le faire:

Math.round(value * 100) / 100

Vous voudrez peut-être continuer et créer une fonction distincte pour le faire pour vous:

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

Ensuite, vous passeriez simplement la valeur.

Vous pouvez l'améliorer pour arrondir à n'importe quel nombre arbitraire de décimales en ajoutant un deuxième paramètre.

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

    return (Math.round(value * multiplier) / multiplier);
}
JayDM
la source
2
Cette solution est erronée, voir stackoverflow.com/questions/38322372/… si vous entrez 156893.145 et arrondissez-la avec la fonction ci-dessus, vous obtenez 156893.14 au lieu de 156893.15 !!!
saimiris_devel
2
@saimiris_devel, vous avez réussi à trouver une instance où la représentation numérique des nombres en JavaScript échoue. Vous avez raison de dire qu'il arrondit incorrectement. Mais - c'est parce que votre numéro d'échantillon multiplié par 100 est déjà cassé (15689314.499999998). En réalité, une réponse complète nécessiterait une bibliothèque qui a été spécialement développée pour prendre en compte les incohérences dans la gestion par JavaScript des nombres réels. Sinon, vous pourriez probablement invalider l'une des réponses qui ont été données à cette question.
JayDM
36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
user3711536
la source
1
Cela ne donnera pas toujours les mêmes résultats que si vous preniez la représentation String de votre nombre et l'arrondissiez. Par exemple +(0.015).toFixed(2) == 0.01,.
Mark Amery
35

Pour moi, Math.round () ne donnait pas la bonne réponse. J'ai trouvé que toFixed (2) fonctionnait mieux. Voici des exemples des deux:

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

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

Vikasdeep Singh
la source
Il est important de noter que toFixed n'effectue pas d'arrondi et que Math.round arrondit simplement au nombre entier le plus proche. Pour conserver les décimales, nous devons donc multiplier le nombre d'origine par le nombre de puissances de dix dont les zéros représentent le nombre de décimales souhaité, puis diviser le résultat par le même nombre. Dans votre cas: Math.round (43000/80000 * 100 * 100) / 100. Enfin toFixed (2) peut être appliqué afin de s'assurer qu'il y a toujours deux décimales dans le résultat (avec des zéros à la fin si nécessaire) - parfait pour aligner à droite une série de nombres présentés verticalement :)
Turbo
7
Il est également important de noter que .toFIxed () génère une chaîne, pas un nombre.
carpiediem
2
Cela ne résout toujours pas l'arrondissement pour 1.005. (1.005).toFixed(2)résulte toujours 1.00.
DPac
34

Utilisez cette fonction Number(x).toFixed(2);

Harish.bazee
la source
8
Enveloppez le tout à Numbernouveau, si vous ne voulez pas qu'il soit renvoyé sous forme de chaîne:Number(Number(x).toFixed(2));
5
L' Numberappel n'est pas nécessaire, x.toFixed(2)fonctionne.
bgusach
3
@bgusach Appel de numéro nécessaire, car l'instruction x.toFixed (2) renvoie une chaîne et non un nombre. Pour convertir à nouveau en nombre, nous devons boucler avec Number
Mohan Ram
2
Lors de l'utilisation de cette méthode (1).toFixed(2)retourne 1.00, mais le questionneur est nécessaire 1dans ce cas.
Eugene Mala
1
Cela ne fonctionne pas, 1.005.toFixed(2)donne "1"quand il devrait l'être "1.01".
Adam Jagosz du
33

2017
Il suffit d'utiliser du code natif.toFixed()

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

Si vous devez être strict et ajouter des chiffres au besoin, il peut utiliser replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
pery mimon
la source
3
La méthode toFixed renvoie une chaîne. Si vous voulez un résultat numérique, vous devrez envoyer le résultat de toFixed à parseFloat.
Zambonilli
@Zambonilli Ou multipliez simplement par 1 si nécessaire. mais parce que le nombre fixe la plupart des cas sont pour l'affichage et non pour la chaîne de calcul, c'est le bon format
pery mimon
2
-1; non seulement toFixedplusieurs réponses ont été suggérées des années avant la vôtre, mais elles ne satisfont pas à la condition «uniquement si nécessaire» de la question; (1).toFixed(2)donne "1.00"où le demandeur a souhaité "1".
Mark Amery
Ok, j'ai compris. J'ajoute également une solution pour ce cas
mimon pery
Si vous utilisez lodash, c'est encore plus simple: _.round (number, decimalPlace) Supprimé mon dernier commentaire, car il a un problème. Lodash _.round fonctionne, cependant. 1,005 avec une décimale de 2 se transforme en 1,01.
Devin Fields
32

Essayez cette solution légère :

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

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222
petermeissner
la source
Quelqu'un sait-il s'il y a une différence entre cela et return Number(x.toFixed(digits))?
1
@JoeRocc ... ne devrait pas faire de différence pour autant que je puisse le voir car .toFixed()n'autorise que les chiffres de toute façon.
petermeissner
4
Cette réponse a le même problème que mentionné plusieurs fois sur cette page. Essayez de round(1.005, 2)voir un résultat de 1au lieu de 1.01.
MilConDoin
semble plus un problème de l'algo d'arrondi? - il y en a plus qu'on pourrait imaginer: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner
31

Il y a plusieurs façons de procéder. Pour les gens comme moi, la variante du 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))
}

Usage:

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

Si votre projet utilise jQuery ou lodash, vous pouvez également trouver round méthode dans les bibliothèques.

Mise à jour 1

J'ai supprimé la variante n.toFixed(2), car elle n'est pas correcte. Merci @ avalanche1

stanleyxu2005
la source
La deuxième option renverra une chaîne avec exactement deux décimales. La question demande des décimales uniquement si nécessaire. La première option est meilleure dans ce cas.
Marcos Lima
@MarcosLima Number.toFixed()renverra une chaîne mais avec un symbole plus devant, l'interpréteur JS convertira la chaîne en un nombre. Ceci est un sucre de syntaxe.
stanleyxu2005
Sur Firefox, alert((+1234).toFixed(2))affiche "1234.00".
Marcos Lima
Sur Firefox, alert(+1234.toFixed(2))lance SyntaxError: identifier starts immediately after numeric literal. Je m'en tiens à la 1ère option.
Marcos Lima
Cela ne fonctionne pas dans certains cas extrêmes : essayez ( jsfiddle ) avec 362.42499999999995. Résultat attendu (comme PHP echo round(362.42499999999995, 2)): 362.43. Résultat réel:362.42
Dr.Gianluigi Zane Zanettini
26

Si vous utilisez la bibliothèque lodash, vous pouvez utiliser la méthode ronde de lodash comme suit.

_.round(number, precision)

Par exemple:

_.round(1.7777777, 2) = 1.78
Madura Pradeep
la source
@Peter L'ensemble des fonctionnalités fournies par Lodash est vraiment bon par rapport au Javascript standard. Cependant, j'ai entendu que Lodash a un problème de performance avec le JS standard. codeburst.io/…
Madura Pradeep
1
J'accepte votre argument selon lequel l'utilisation de lodash présente des inconvénients en termes de performances. Je pense que ces problèmes sont communs à de nombreuses abstractions. Mais regardez simplement combien de réponses il y a sur ce fil et comment les solutions intuitives échouent pour les cas marginaux. Nous avons vu ce modèle avec jQuery et le problème racine a été résolu lorsque les navigateurs ont adopté une norme commune qui a résolu la plupart de nos cas d'utilisation. Les goulots d'étranglement des performances ont ensuite été déplacés vers les moteurs de navigation. Je pense que la même chose devrait arriver au lodash. :)
Peter
26

Depuis ES6, il existe un moyen «approprié» (sans remplacer les statiques et sans créer de solutions de contournement) de le faire en utilisant 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));

alors vous pouvez juste parseFloatet les zéros «disparaîtront».

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

Cela ne résout pas le `` problème d'arrondi 1.005 '', car il est intrinsèque à la façon dont les fractions flottantes sont traitées .

console.log(1.005 - 0.005);

Si vous êtes ouvert aux bibliothèques, vous pouvez utiliser 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>

Matas Vaitkevicius
la source
3
(1.005).toPrecision(3)retourne toujours 1.00au lieu de 1.01réellement.
Giacomo
toPrecisionrenvoie une chaîne qui modifie le type de sortie souhaité.
adamduren
@Giacomo Ce n'est pas un défaut de .toPrecisionméthode, c'est une spécificité des nombres à virgule flottante (quels sont les nombres en JS) - essayez 1.005 - 0.005, ça reviendra 0.9999999999999999.
shau-kote
1
(1).toPrecision(3)renvoie «1,00», mais le questionneur voulait avoir 1dans ce cas.
Eugene Mala
1
Comme l'a dit @Giacomo, cette réponse semble confondre "chiffres significatifs" avec "arrondi à un nombre de décimales". toPrecisionfait le format, pas ce dernier, et n'est pas une réponse à la question du PO, même si cela peut sembler à première vue pertinent, il se trompe beaucoup. Voir en.wikipedia.org/wiki/Significant_figures . Par exemple, Number(123.4).toPrecision(2)retourne "1.2e+2"et Number(12.345).toPrecision(2)retourne "12". Je serais également d'accord avec le point de @ adamduren selon lequel il renvoie une chaîne qui n'est pas souhaitable (pas un gros problème mais pas souhaitable).
Neek
23

MarkG et Lavamantis ont offert une bien meilleure solution que celle qui a été acceptée. C'est dommage qu'ils n'obtiennent pas plus de votes positifs!

Voici la fonction que j'utilise pour résoudre les problèmes de décimales à virgule flottante également basée sur MDN . Elle est encore plus générique (mais moins concise) que la solution de 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));
}

Utilisez-le avec:

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

Par rapport à la solution de Lavamantis, nous pouvons faire ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123
astorije
la source
2
Votre solution ne couvre pas certains cas contrairement à la solution de MDN. Bien qu'il puisse être plus court, il n'est pas précis ...
astorije
1
round (-1835.665,2) => -1835.66
Jorge Sampayo
21

Cela peut vous aider:

var result = Math.round(input*100)/100;

pour plus d'informations, vous pouvez consulter ce lien

Math.round (num) vs num.toFixed (0) et incohérences du navigateur

totten
la source
1
Pourquoi dans le monde la réponse acceptée a-t-elle autant de voix que celle-ci, car elles sont pratiquement la même chose, mais celle-ci a été publiée 1 minute après celle acceptée?
Citez Dave
18

L'approche la plus simple serait d'utiliser toFixed puis de supprimer les zéros de fin à l'aide de la fonction Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
Marcin Wanago
la source
cela ne fonctionne pas pour tous les cas. faites des tests approfondis avant de poster des réponses.
baburao
@baburao Veuillez poster un cas dans lequel la solution ci-dessus ne fonctionne pas
Marcin Wanago
nombre const = 15; Numéro (number.toFixed (2)); //15,00 au lieu de 15
Kevin Jhangiani
1
@KevinJhangiani const nombre = 15; Numéro (number.toFixed (2)); // 15 - Je l'ai testé sur Chrome et Firefox les plus récents
Marcin Wanago
@KevinJhangiani comment obtenez-vous 15.00? Les nombres dans JS ne stockent pas les décimales et tout affichage tronque automatiquement les décimales en excès (tous les zéros à la fin).
VLAZ
16
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) ici 2 est le nombre de chiffres jusqu'à ce que nous voulons arrondir ce nombre.

Ritesh Dhuri
la source
ce .toFixed () est plus simple à implémenter. il suffit de le parcourir une fois.
Ritesh Dhuri
14

Manière la plus simple:

+num.toFixed(2)

Il le convertit en chaîne, puis de nouveau en entier / flottant.

grosse Pomme de terre
la source
Merci pour cette réponse la plus simple. Cependant, qu'est-ce que '+' dans + num? Cela n'a pas fonctionné pour moi où la valeur décimale est entrée dans la chaîne. J'ai fait: (num * 1) .toFixed (2).
Ethan
@momo change simplement l'argument toFixed()en 3. Il en serait ainsi +num.toFixed(3). Cela fonctionne comme il est censé le faire, 1,005 est arrondi à 1,00, ce qui équivaut à 1
bigpotato
1
@Edmund Il est censé renvoyer 1.01, pas 1.00
mmm
13

Voici une méthode prototype:

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

var yournum = 10.55555;
yournum = yournum.round(2);
arielf
la source
13

Utilisez quelque chose comme ceci "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
Arulraj
la source
1
pas si l'inexactitude est intrinsèque à la représentation flottante. vous le supprimeriez et réintroduiriez la même erreur en reconvertissant en float!
Ben McIntyre du
12

Une façon d'obtenir un tel arrondi uniquement si nécessaire est d'utiliser Number.prototype.toLocaleString () :

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

Cela fournira exactement la sortie que vous attendez, mais sous forme de chaînes. Vous pouvez toujours les reconvertir en nombres si ce n'est pas le type de données que vous attendez.

Javarome
la source
C'est de loin la solution la plus propre et évite tous les problèmes de virgule flottante compliqués, mais la prise en charge par MDN est toujours incomplète - Safari ne prend pas encore en charge la transmission d'arguments toLocaleString.
Mark Amery
@MarkAmery Pour l'instant, seul le navigateur Android a des problèmes: caniuse.com/#search=toLocaleString
ptyskju
12

Après avoir parcouru diverses itérations de toutes les manières possibles d'obtenir une précision d'arrondi décimale vraie et précise, il est clair que la solution la plus précise et la plus efficace consiste à utiliser Number.EPSILON. Cela fournit une véritable solution mathématique au problème de la précision mathématique à virgule flottante. Il peut être facilement polyfilled comme indiqué ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON pour prendre en charge tous les derniers utilisateurs IE restants (là encore, peut-être que nous devrait cesser de le faire).

Adapté de la solution fournie ici: https://stackoverflow.com/a/48850944/6910392

Une solution simple qui fournit un arrondi décimal, un sol et un plafond précis, avec une variable de précision en option sans ajouter une bibliothèque entière.

MISE À JOUR: Comme Sergey l'a noté dans les commentaires, il y a une limitation à cette (ou toute) méthode qui mérite d'être signalée. Dans le cas de nombres comme 0,01499999999999999999, vous rencontrerez toujours des inexactitudes qui sont le résultat de l'atteinte du bord absolu des limitations de précision pour le stockage de valeurs à virgule flottante. Il n'y a pas de solution mathématique ou autre qui peut être appliquée pour expliquer cela, car la valeur elle-même est immédiatement évaluée à 0,015. Vous pouvez le confirmer en invoquant simplement cette valeur par elle-même dans la console. En raison de cette limitation, il ne serait même pas possible d'utiliser la manipulation de chaîne pour réduire cette valeur, car sa représentation sous forme de chaîne est simplement "0,015". Toute solution pour en tenir compte devrait être appliquée logiquement à la source des données avant d'accepter la valeur dans un 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));

KFish
la source
1
(DecimalPrecision.round (0.014999999999999999, 2)) // renvoie 0,02
Sergey
Bonne prise! Le problème est avec le stockage à virgule flottante dans JS, il y aura toujours des cas marginaux. La bonne nouvelle est que les calculs que vous appliquez à Number.EPSILON peuvent être plus finement ajustés pour pousser ces cas de bord plus loin. Si vous ne voulez garantir aucune possibilité pour les cas marginaux, votre seule vraie solution sera la manipulation de chaînes, puis les mathématiques. Au moment où vous effectuez un calcul mathématique sur la valeur (même en essayant de déplacer la décimale), vous avez déjà produit le bogue.
KFish
En fait, à la suite d'une inspection plus approfondie, cela n'est pas dû aux calculs impliqués, mais plutôt au problème qui se manifeste immédiatement lors de l'appel de la valeur spécifiée. Vous pouvez le confirmer simplement en tapant ce numéro dans la console et voyez qu'il est immédiatement évalué à 0,015. Par conséquent, cela représenterait le bord absolu de précision pour tout nombre à virgule flottante dans JS. Dans ce cas, vous ne pouvez même pas convertir en chaîne et manipuler car la valeur de chaîne serait "0,015"
KFish
11

C'est la solution la plus simple et la plus élégante (et je suis le meilleur du monde;):

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
Soldeplata Saketos
la source
4
C'est une bonne façon de réécrire la réponse acceptée pour accepter un argument en utilisant la Enotation.
AxelH
1
Cela ne fonctionne pas dans certains cas extrêmes : essayez ( jsfiddle ) roundToX(362.42499999999995, 2). Résultat attendu (comme PHP echo round(362.42499999999995, 2)): 362.43. Résultat réel:362.42
Dr.Gianluigi Zane Zanettini
6
À mon humble avis, votre résultat PHP est faux. Peu importe ce qui vient après la troisième décimale, si la troisième décimale est inférieure à 5, la deuxième décimale doit rester la même. Voilà la définition mathématique.
Soldeplata Saketos
1
Pour être encore plus concis, «e +» peut simplement être «e» à la place.
Lonnie Best