Tronquer (pas arrondir) les nombres décimaux en javascript


93

J'essaye de tronquer les nombres décimaux en décimales. Quelque chose comme ça:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)fait à peu près ce qu'il faut mais arrondit la valeur. Je n'ai pas besoin d'arrondir la valeur. J'espère que cela est possible en javascript.


6
jQuery n'est qu'un framework et votre problème n'est pas lié à jQuery. Il s'agit davantage de faire des calculs de base en JavaScript. J'espère que vous êtes également satisfait d'une solution non-jQuery.
Felix Kling le

J'ai trouvé que c'était trop de travail pour obtenir mes calculs pour renvoyer seulement 2 décimales en utilisant Javascript. J'ai pu le faire facilement dans ma vue de base de données à la place. Je me rends compte que cette méthode ne conviendra pas à toutes les situations, mais je veux la présenter ici car elle pourrait faire gagner beaucoup de temps à quelqu'un.
MsTapp

Réponses:


51

upd :

Donc, après tout, il s'est avéré que les bugs arrondis vous hanteront toujours, peu importe à quel point vous essayez de les compenser. Par conséquent, le problème doit être attaqué en représentant les nombres exactement en notation décimale.

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

[   5.467.toFixedDown(2),
    985.943.toFixedDown(2),
    17.56.toFixedDown(2),
    (0).toFixedDown(1),
    1.11.toFixedDown(1) + 22];

// [5.46, 985.94, 17.56, 0, 23.1]

Ancienne solution sujette aux erreurs basée sur la compilation des autres:

Number.prototype.toFixedDown = function(digits) {
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

4
Oui, les prototypes ne fonctionnent pas de manière fiable entre les navigateurs. Au lieu de définir cette fonction (à usage limité) à travers le système de types, d'une manière qui ne fonctionne pas de manière fiable, pourquoi ne pas simplement la mettre dans une bibliothèque.
Thomas W

3
Cela ne fonctionne pas comme excepté. Essayez le nombre 17,56 et chiffres = 2. Cela devrait être 17,56, mais cette fonction renvoie 17,55.
shendz

2
Deux incohérences avec cette fonction: Cette fonction renvoie une chaîne de sorte qu'elle 1.11.toFixedDown(1) + 22se termine comme 1.122au lieu de 23.1. Devrait aussi 0.toFixedDown(1)produire 0mais à la place il produit -0.1.
Nick Knowlson

5
Notez que cette fonction supprime le signe négatif. Ex: (-10.2131).toFixedDown(2) // ==> 10.21.
rgajrawala

4
En outre, (1e-7).toFixedDown(0) // ==> 1e-7. Est - ce que pour 1e-(>=7)(ex: 1e-8, 1e-9, ...).
rgajrawala

60

La réponse de Dogbert est bonne, mais si votre code doit gérer des nombres négatifs, il Math.floorpeut à lui seul donner des résultats inattendus.

Par exemple Math.floor(4.3) = 4, maisMath.floor(-4.3) = -5

Utilisez plutôt une fonction d'aide comme celle-ci pour obtenir des résultats cohérents:

truncateDecimals = function (number) {
    return Math[number < 0 ? 'ceil' : 'floor'](number);
};

// Applied to Dogbert's answer:
var a = 5.467;
var truncated = truncateDecimals(a * 100) / 100; // = 5.46

Voici une version plus pratique de cette fonction:

truncateDecimals = function (number, digits) {
    var multiplier = Math.pow(10, digits),
        adjustedNum = number * multiplier,
        truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

    return truncatedNum / multiplier;
};

// Usage:
var a = 5.467;
var truncated = truncateDecimals(a, 2); // = 5.46

// Negative digits:
var b = 4235.24;
var truncated = truncateDecimals(b, -2); // = 4200

Si ce n'est pas le comportement souhaité, insérez un appel à Math.abssur la première ligne:

var multiplier = Math.pow(10, Math.abs(digits)),

EDIT: shendz souligne correctement que l'utilisation de cette solution avec a = 17.56produira incorrectement 17.55. Pour en savoir plus sur les raisons de cette situation, lisez Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante . Malheureusement, écrire une solution qui élimine toutes les sources d'erreur en virgule flottante est assez délicat avec javascript. Dans une autre langue, vous utiliseriez des entiers ou peut-être un type décimal, mais avec javascript ...

Cette solution doit être précise à 100%, mais elle sera également plus lente:

function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}

Pour ceux qui ont besoin de vitesse mais qui veulent également éviter les erreurs en virgule flottante, essayez quelque chose comme BigDecimal.js . Vous pouvez trouver d'autres bibliothèques javascript BigDecimal dans cette question SO: "Existe-t-il une bonne bibliothèque Javascript BigDecimal?" et voici un bon article de blog sur les bibliothèques mathématiques pour Javascript


Pourquoi inattendu? Changer la direction de l'arrondi lorsque vous descendez en dessous de 0, provoque toutes sortes d'artefacts arithmétiques et de mathématiques merdiques. Par exemple, deux fois plus de nombres arrondiront à 0 que tout autre entier. Pour les graphiques, la comptabilité et bien d'autres utilisations, vous obtiendrez des résultats merdiques. Pour vous dire la vérité, ce serait plus difficile de dire ce que votre suggestion est bon aussi de dire ce qu'il est pas .
Thomas W

C'est bon pour exactement ce qu'il dit - lorsque vous voulez tronquer les décimales plutôt que l'arrondir.
Nick Knowlson le

1
Ne fonctionnera pas avec 17,56 car le navigateur donne 17,56 * 100 = 1755,9999999999998 et non 1756
shendz

Bon point shendz. J'ai mis à jour ma réponse avec une solution qui élimine toute erreur en virgule flottante pour ceux qui en ont besoin.
Nick Knowlson

1
Cela ne fonctionnera pas pour les nombres inférieurs à 1 si vous ne voulez pas de décimales - truncateDecimals (.12345, 0) donne NaN sauf si vous ajoutez une vérification: cela if(isNAN(result) result = 0; dépend du comportement souhaité.
Jeremy Witmer

35
var a = 5.467;
var truncated = Math.floor(a * 100) / 100; // = 5.46

5
Cela fonctionne bien mais donnera des résultats qui ne sont probablement pas souhaitables s'il (ou quelqu'un d'autre regardant cette réponse plus tard) doit faire face à des nombres négatifs. Voir stackoverflow.com/a/9232092/224354
Nick Knowlson

3
Pourquoi indésirable? Changer la direction de l'arrondi lorsque vous passez en dessous de 0, provoque toutes sortes d'artefacts arithmétiques.
Thomas W

8
Il y a une différence entre l'arrondi et la troncature. La troncature est clairement le comportement recherché par cette question. Si j'appelle truncate(-3.14)et reçois en -4retour, j'appellerais certainement cela indésirable.
Nick Knowlson

Je suis d'accord avec Thomas. La différence de perspective peut venir du fait que vous tronquiez habituellement pour l'affichage ou pour le calcul. D'un point de vue informatique, cela évite les "arithmétiques arithmétiques"

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Par conséquent, ce n'est pas une solution correcte
Sanyam Jain

22

Vous pouvez corriger l'arrondi en soustrayant 0,5 pour toFixed, par exemple

(f - 0.005).toFixed(2)

1
Attention: comme c'est le cas, cela ne fonctionne pas pour les très petits nombres, les nombres avec plus de 3 décimales ou les nombres négatifs. Essayez .0045, 5.4678 et -5.467
Nick Knowlson

Cela fonctionnera tant que vous correspondez à la valeur que vous soustrayez avec la longueur que vous souhaitez avoir. tout ce que vous passez à toFixed () doit être le nombre de 0 après la décimale.
dmarra

18

Pensez à tirer profit de la double tilde:~~ .

Prenez le nombre. Multipliez par les chiffres significatifs après la décimale afin de pouvoir tronquer à zéro place avec ~~. Divisez ce multiplicateur. Profit.

function truncator(numToTruncate, intDecimalPlaces) {    
    var numPower = Math.pow(10, intDecimalPlaces); // "numPowerConverter" might be better
    return ~~(numToTruncate * numPower)/numPower;
}

J'essaye de résister à envelopper l' ~~appel dans des parens; l'ordre des opérations devrait faire en sorte que cela fonctionne correctement, je crois.

alert(truncator(5.1231231, 1)); // is 5.1

alert(truncator(-5.73, 1)); // is -5.7

alert(truncator(-5.73, 0)); // is -5

Lien JSFiddle .

EDIT: Avec le recul, j'ai involontairement traité des cas pour arrondir également la gauche de la décimale.

alert(truncator(4343.123, -2)); // gives 4300.

La logique est un peu farfelue à la recherche de cet usage et peut bénéficier d'une refactorisation rapide. Mais ça marche toujours. Mieux vaut avoir de la chance que du bien.


C'est la meilleure réponse. Si vous étendez le Mathprototype avec ceci et vérifiez les NaN-s avant de l'exécuter, ce serait tout simplement parfait.
Bartłomiej Zalewski

truncator((10 * 2.9) / 100, 2)return 0.28 au lieu de 0.29 ... jsfiddle.net/25tgrzq1
Alex

14

Belle solution en une ligne:

function truncate (num, places) {
  return Math.trunc(num * Math.pow(10, places)) / Math.pow(10, places);
}

Alors appelez-le avec:

truncate(3.5636232, 2); // returns 3.56
truncate(5.4332312, 3); // returns 5.433
truncate(25.463214, 4); // returns 25.4632

2
J'aime cette solution, mais gardez simplement à l'esprit qu'elle n'est pas entièrement prise en charge par tous les navigateurs. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Gene Parcellano

11

J'ai pensé ajouter une réponse en utilisant |car il est simple et fonctionne bien.

truncate = function(number, places) {
  var shift = Math.pow(10, places);

  return ((number * shift) | 0) / shift;
};

1
Bon appel. L'utilisation d'un opérateur au niveau du bit contraint la valeur à un int, et oring avec 0 signifie "juste garder ce que j'ai déjà". Fait ce que ma ~~réponse fait, mais avec une seule opération au niveau du bit. Bien qu'il ait la même limitation que celle écrite aussi: nous ne pouvons pas dépasser 2 ^ 31 .
ruffin

1
pas correct lorsque truncate((10 * 2.9) / 100);ce code renvoie 0,28 au lieu de 0,29 jsfiddle.net/9pf0732d
Alex

@Alex Comme je suppose que vous vous en rendez compte ... bienvenue dans JavaScript! . Il y a des correctifs. Vous aimeriez peut-être en partager un? : D
ruffin

@ruffin Je connais ce problème =) Je pensais que cette réponse était la solution à ce problème. Malheureusement, je n'ai pas encore trouvé de solution exacte, partout il y a un tel problème.
Alex


7

La réponse de @ Dogbert peut être améliorée avec Math.trunc, qui tronque au lieu d'arrondir.

Il y a une différence entre l'arrondi et la troncature. La troncature est clairement le comportement recherché par cette question. Si j'appelle truncate (-3.14) et reçois -4 en retour, j'appellerais certainement cela indésirable. - @NickKnowlson

var a = 5.467;
var truncated = Math.trunc(a * 100) / 100; // = 5.46
var a = -5.467;
var truncated = Math.trunc(a * 100) / 100; // = -5.46

1
Cela ne fonctionne pas dans tous les cas, c'est-à-dire console.log (Math.trunc (9.28 * 100) / 100); // 9.27
Mike Makuch

@MikeMakuch ce n'est pas un problème Math.trunc, mais plutôt que 9.28 * 100est 927.9999plutôt que 928. Vous voudrez peut-être lire les dangers de la
virgule

5

J'ai écrit une réponse en utilisant une méthode plus courte. Voici ce que j'ai trouvé

function truncate(value, precision) {
    var step = Math.pow(10, precision || 0);
    var temp = Math.trunc(step * value);

    return temp / step;
}

La méthode peut être utilisée comme ça

truncate(132456.25456789, 5)); // Output: 132456.25456
truncate(132456.25456789, 3)); // Output: 132456.254
truncate(132456.25456789, 1)); // Output: 132456.2   
truncate(132456.25456789));    // Output: 132456

Ou, si vous voulez une syntaxe plus courte, c'est parti

function truncate(v, p) {
    var s = Math.pow(10, p || 0);
    return Math.trunc(s * v) / s;
}

c'est la méthode que je m'attendais à utiliser
taxilien le

4
Number.prototype.trim = function(decimals) {
    var s = this.toString();
    var d = s.split(".");
    d[1] = d[1].substring(0, decimals);
    return parseFloat(d.join("."));
}

console.log((5.676).trim(2)); //logs 5.67

J'aime que cela fonctionne avec des chaînes, éliminant ainsi les nuances des nombres à virgule flottante. Merci!
iCode

4

Je pense que cette fonction pourrait être une solution simple:

function trunc(decimal,n=2){
  let x = decimal + ''; // string 
  return x.lastIndexOf('.')>=0?parseFloat(x.substr(0,x.lastIndexOf('.')+(n+1))):decimal; // You can use indexOf() instead of lastIndexOf()
}

console.log(trunc(-241.31234,2));
console.log(trunc(241.312,5));
console.log(trunc(-241.233));
console.log(trunc(241.2,0));  
console.log(trunc(241));


Deux ans après, cela a été publié, mais je suis tombé dessus quand j'essayais de trouver la meilleure façon d'utiliser Math.trunc, regex, etc. Mort simple mais fonctionne parfaitement (pour mon cas d'utilisation en tout cas).
giles123

N'oubliez pas de tenir compte de n = 0.
giles123

3

J'ai trouvé un problème: vu la situation suivante: 2.1 ou 1.2 ou -6.4

Que faire si vous voulez toujours 3 décimales ou deux ou n'importe quoi, donc, vous devez compléter les zéros de tête à droite

// 3 decimals numbers
0.5 => 0.500

// 6 decimals
0.1 => 0.10000

// 4 decimales
-2.1 => -2.1000

// truncate to 3 decimals
3.11568 => 3.115

C'est la fonction fixe de Nick Knowlson

function truncateDecimals (num, digits) 
{
    var numS = num.toString();
    var decPos = numS.indexOf('.');
    var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;
    var trimmedResult = numS.substr(0, substrLength);
    var finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    // adds leading zeros to the right
    if (decPos != -1){
        var s = trimmedResult+"";
        decPos = s.indexOf('.');
        var decLength = s.length - decPos;

            while (decLength <= digits){
                s = s + "0";
                decPos = s.indexOf('.');
                decLength = s.length - decPos;
                substrLength = decPos == -1 ? s.length : 1 + decPos + digits;
            };
        finalResult = s;
    }
    return finalResult;
};

https://jsfiddle.net/huttn155/7/


x = 0.0000le test truncateDecimals (x, 2)échoue. revient 0. pas comme prévu0.00
Ling Loeng

3
function toFixed(number, digits) {
    var reg_ex = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)")
    var array = number.toString().match(reg_ex);
    return array ? parseFloat(array[1]) : number.valueOf()
}

var test = 10.123456789
var __fixed = toFixed(test, 6)
console.log(__fixed)
// => 10.123456

3

La réponse de @kirilloid semble être la bonne réponse, cependant, le code principal doit être mis à jour. Sa solution ne prend pas en charge les nombres négatifs (que quelqu'un a mentionnés dans la section des commentaires mais n'a pas été mis à jour dans le code principal).

Mettre à jour cela vers une solution finale testée complète:

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("([-]*\\d+\\.\\d{" + digits + "})(\\d)"),
    m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

Utilisation de l'échantillon:

var x = 3.1415629;
Logger.log(x.toFixedDown(2)); //or use whatever you use to log

Violon: Numéro JS Arrondi vers le bas

PS: pas assez de repo pour commenter cette solution.


2

Voici une fonction simple mais fonctionnelle pour tronquer le nombre jusqu'à 2 décimales.

           function truncateNumber(num) {
                var num1 = "";
                var num2 = "";
                var num1 = num.split('.')[0];
                num2 = num.split('.')[1];
                var decimalNum = num2.substring(0, 2);
                var strNum = num1 +"."+ decimalNum;
                var finalNum = parseFloat(strNum);
                return finalNum;
            }

2

Le type résultant reste un nombre ...

/* Return the truncation of n wrt base */
var trunc = function(n, base) {
    n = (n / base) | 0;
    return base * n;
};
var t = trunc(5.467, 0.01);

2

Lodash a quelques méthodes utilitaires mathématiques qui peuvent arrondir , plancher et plafonner un nombre à une précision décimale donnée. Cela laisse des zéros à la fin.

Ils adoptent une approche intéressante, en utilisant l'exposant d'un nombre. Apparemment, cela évite les problèmes d'arrondi.

(Remarque: funcest Math.roundou ceilou floordans le code ci-dessous)

// Shift with exponential notation to avoid floating-point issues.
var pair = (toString(number) + 'e').split('e'),
    value = func(pair[0] + 'e' + (+pair[1] + precision));

pair = (toString(value) + 'e').split('e');
return +(pair[0] + 'e' + (+pair[1] - precision));

Lien vers le code source


1

Voici mon point de vue sur le sujet:

convert.truncate = function(value, decimals) {
  decimals = (decimals === undefined ? 0 : decimals);
  return parseFloat((value-(0.5/Math.pow(10, decimals))).toFixed(decimals),10);
};

C'est juste une version légèrement plus élaborée de

(f - 0.005).toFixed(2)

1

Celui qui est marqué comme la solution est la meilleure solution que j'ai trouvée jusqu'à aujourd'hui, mais a un problème sérieux avec 0 (par exemple, 0.toFixedDown (2) donne -0,01). Je suggère donc d'utiliser ceci:

Number.prototype.toFixedDown = function(digits) {
  if(this == 0) {
    return 0;
  }
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

1

Voici ce que j'utilise:

var t = 1;
for (var i = 0; i < decimalPrecision; i++)
    t = t * 10;

var f = parseFloat(value);
return (Math.floor(f * t)) / t;

1
const TO_FIXED_MAX = 100;

function truncate(number, decimalsPrecison) {
  // make it a string with precision 1e-100
  number = number.toFixed(TO_FIXED_MAX);

  // chop off uneccessary digits
  const dotIndex = number.indexOf('.');
  number = number.substring(0, dotIndex + decimalsPrecison + 1);

  // back to a number data type (app specific)
  return Number.parseFloat(number);
}

// example
truncate(0.00000001999, 8);
0.00000001

marche avec:

  • nombres négatifs
  • très petits nombres (précision Number.EPSILON)

0

juste pour souligner une solution simple qui a fonctionné pour moi

convertissez-le en chaîne puis regexez-le ...

var number = 123.45678;
var number_s = '' + number;
var number_truncated_s = number_s.match(/\d*\.\d{4}/)[0]
var number_truncated = parseFloat(number_truncated_s)

Il peut être abrégé en

var number_truncated = parseFloat(('' + 123.4568908).match(/\d*\.\d{4}/)[0])

0

Voici un code ES6 qui fait ce que vous voulez

const truncateTo = (unRouned, nrOfDecimals = 2) => {
      const parts = String(unRouned).split(".");

      if (parts.length !== 2) {
          // without any decimal part
        return unRouned;
      }

      const newDecimals = parts[1].slice(0, nrOfDecimals),
        newString = `${parts[0]}.${newDecimals}`;

      return Number(newString);
    };

// your examples 

 console.log(truncateTo(5.467)); // ---> 5.46

 console.log(truncateTo(985.943)); // ---> 985.94

// other examples 

 console.log(truncateTo(5)); // ---> 5

 console.log(truncateTo(-5)); // ---> -5

 console.log(truncateTo(-985.943)); // ---> -985.94


0
Number.prototype.truncate = function(places) {
  var shift = Math.pow(10, places);

  return Math.trunc(this * shift) / shift;
};

0

Vous pouvez travailler avec des chaînes. Il vérifie si "." existe, puis supprime une partie de la chaîne.

tronquer (7,88, 1) -> 7,8

tronquer (7.889, 2) -> 7.89

tronquer (-7,88, 1) -> -7,88

function  truncate(number, decimals) {
    const tmp = number + '';
    if (tmp.indexOf('.') > -1) {
        return +tmp.substr(0 , tmp.indexOf('.') + decimals+1 );
    } else {
        return +number
    }
 }

0

Je ne comprends pas vraiment pourquoi il y a tant de réponses différentes à une question aussi fondamentalement simple; il n'y a que deux approches que j'ai vues qui semblent valoir la peine d'être examinées. J'ai fait un test rapide pour voir la différence de vitesse en utilisant https://jsbench.me/ .

C'est la solution qui est actuellement (26/09/2020) signalée comme la réponse:

function truncate(n, digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = n.toString().match(re);
    return m ? parseFloat(m[1]) : n.valueOf();
};

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];

Cependant, cela fait des trucs de chaînes et d'expressions régulières, ce qui n'est généralement pas très efficace, et il existe une fonction Math.trunc qui fait exactement ce que l'OP veut, sans décimales. Par conséquent, vous pouvez facilement utiliser cela plus un peu d'arithmétique supplémentaire pour obtenir la même chose.

Voici une autre solution que j'ai trouvée sur ce fil, qui est celle que j'utiliserais:

function truncate(n, digits) {
    var step = Math.pow(10, digits || 0);
    var temp = Math.trunc(step * n);

    return temp / step;
}

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];
    

La première méthode est "99,92% plus lente" que la seconde, donc la seconde est définitivement celle que je recommanderais d'utiliser.

Bon, revenons à la recherche d'autres moyens d'éviter le travail ...

capture d'écran du benchmark

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.