Comment puis-je analyser une chaîne avec un séparateur de milliers de virgules en un nombre?

104

J'ai 2,299.00une chaîne et j'essaye de l'analyser en un nombre. J'ai essayé d'utiliser parseFloat, ce qui donne 2. Je suppose que la virgule est le problème, mais comment résoudre ce problème de la bonne manière? Supprimez simplement la virgule?

var x = parseFloat("2,299.00")
alert(x);

user1540714
la source

Réponses:

121

Oui, supprimez les virgules:

parseFloat(yournumber.replace(/,/g, ''));
Sam
la source
7
Ouais, mais maintenant les décimales sont perdues. 2300,00 donne 2300 par exemple.
user1540714
@ user1540714 c'est parce que c'est un flottant plutôt qu'une chaîne. Si vous devez ensuite le sortir, vous devez le formater pour toujours afficher 2 décimales.
Jon Taylor
Cela peut-il être évité?
Je
38
Puis-je suggérer que nous capturions à la place toutes les virgules en utilisant: .replace(/,/g, '') Cette regex utilise le global gpour remplacer tout.
gdibble
32
Je ne sais pas pourquoi cette réponse a reçu autant de votes positifs et a été choisie comme correcte, elle présente deux problèmes graves. 1. Il ne peut pas gérer les nombres avec plusieurs virgules, par exemple les parseFloat("2,000,000.00".replace(',',''))retours 2000et 2. il échoue dans de nombreuses régions du monde où se ,trouve une décimale, par exemple les parseFloat("2.000.000,00".replace(',',''))retours 2- voir ma réponse ci-dessous pour quelque chose qui fonctionne partout dans le monde.
David Meister
113

La suppression des virgules est potentiellement dangereuse car, comme d'autres l'ont mentionné dans les commentaires, de nombreux paramètres régionaux utilisent une virgule pour signifier quelque chose de différent (comme une décimale).

Je ne sais pas d'où tu as obtenu ta chaîne, mais dans certains endroits du monde "2,299.00"=2.299

L' Intlobjet aurait pu être un bon moyen de résoudre ce problème, mais d'une manière ou d'une autre, ils ont réussi à expédier la spécification avec seulement une Intl.NumberFormat.format()API et aucune parsecontrepartie :(

La seule façon d'analyser une chaîne contenant des caractères numériques culturels en un nombre reconnaissable par la machine d'une manière sensée i18n est d'utiliser une bibliothèque qui exploite les données CLDR pour couvrir toutes les manières possibles de formater les chaînes numériques http: //cldr.unicode. org /

Les deux meilleures options JS que j'ai rencontrées pour cela jusqu'à présent:

David Meister
la source
4
Je ne peux pas croire que personne n'a encore voté pour cette réponse, c'est la seule réponse réelle sur cette page!
evilkos
9
Tout à fait d'accord qu'Intl aurait dû avoir une contrepartie d'analyse. Il semble évident que les gens en auraient besoin.
carlossless du
C'est la seule façon systématique de le faire. Impossible d'y parvenir avec une seule approche regex pour tous. Les virgules et les points ont des significations différentes dans différentes langues.
Wildhammer
38

Sur les navigateurs modernes, vous pouvez utiliser l' Intl.NumberFormat intégré pour détecter le formatage des nombres du navigateur et normaliser l'entrée pour qu'elle corresponde.

function parseNumber(value, locale = navigator.language) {
  const example = Intl.NumberFormat(locale).format('1.1');
  const cleanPattern = new RegExp(`[^-+0-9${ example.charAt( 1 ) }]`, 'g');
  const cleaned = value.replace(cleanPattern, '');
  const normalized = cleaned.replace(example.charAt(1), '.');

  return parseFloat(normalized);
}

const corpus = {
  '1.123': {
    expected: 1.123,
    locale: 'en-US'
  },
  '1,123': {
    expected: 1123,
    locale: 'en-US'
  },
  '2.123': {
    expected: 2123,
    locale: 'fr-FR'
  },
  '2,123': {
    expected: 2.123,
    locale: 'fr-FR'
  },
}


for (const candidate in corpus) {
  const {
    locale,
    expected
  } = corpus[candidate];
  const parsed = parseNumber(candidate, locale);

  console.log(`${ candidate } in ${ corpus[ candidate ].locale } == ${ expected }? ${ parsed === expected }`);
}

Il y a évidemment place pour une optimisation et une mise en cache, mais cela fonctionne de manière fiable dans toutes les langues.

Paul Alexander
la source
Pourquoi cela n'a-t-il pas reçu beaucoup de votes positifs !!! C'est la solution la plus élégante pour INPUT dans les formats internationaux! Je vous remercie!!
kpollock
en France, la devise est 23113413,12
stackdave
26

Supprimez tout ce qui n'est pas un chiffre, un point décimal ou un signe moins ( -):

var str = "2,299.00";
str = str.replace(/[^\d\.\-]/g, ""); // You might also include + if you want them to be able to type it
var num = parseFloat(str);

Violon mis à jour

Notez que cela ne fonctionnera pas pour les nombres en notation scientifique. Si vous le souhaitez, changer la replaceligne à ajouter e, Eet +à la liste des caractères acceptables:

str = str.replace(/[^\d\.\-eE+]/g, "");
TJ Crowder
la source
4
Cela ne fonctionnera pas pour les nombres négatifs ou les nombres en notation scientifique.
Aadit M Shah le
1
str = str.replace(/(\d+),(?=\d{3}(\D|$))/g, "$1"); C'est ce que j'utiliserais, mais je suis totalement inutile à regex et c'est quelque chose que j'ai trouvé il y a quelque temps sur un autre fil SO.
Jon Taylor
@JonTaylor: Le but ici n'était pas de valider le numéro, juste de le faire fonctionner parseFloat- ce qui le validera. :-)
TJ Crowder
ce que le mien fait, pour autant que je sache. Je l'utilise pour convertir 1 234 567, etc. en 1234567. Comme je l'ai dit, je suis tout à fait inutile à regex donc je ne pourrais pas pour la vie de moi vous dire ce qu'il fait réellement lol.
Jon Taylor
1
mauvaise idée de supprimer "-" signe moins - obtenir complètement un autre nombre, et aussi si vous analysez des formats hétérogènes, "7.500"! == "7,500"
Serge
14

Vous devriez généralement envisager d'utiliser des champs de saisie qui n'autorisent pas la saisie de texte libre pour les valeurs numériques. Mais il peut y avoir des cas où vous devez deviner le format d'entrée. Par exemple, 1,234,56 en Allemagne signifie 1 234,56 aux États-Unis. Voir https://salesforce.stackexchange.com/a/21404 pour une liste des pays qui utilisent la virgule comme décimal.

J'utilise la fonction suivante pour faire une meilleure estimation et supprimer tous les caractères non numériques:

function parseNumber(strg) {
    var strg = strg || "";
    var decimal = '.';
    strg = strg.replace(/[^0-9$.,]/g, '');
    if(strg.indexOf(',') > strg.indexOf('.')) decimal = ',';
    if((strg.match(new RegExp("\\" + decimal,"g")) || []).length > 1) decimal="";
    if (decimal != "" && (strg.length - strg.indexOf(decimal) - 1 == 3) && strg.indexOf("0" + decimal)!==0) decimal = "";
    strg = strg.replace(new RegExp("[^0-9$" + decimal + "]","g"), "");
    strg = strg.replace(',', '.');
    return parseFloat(strg);
}   

Essayez-le ici: https://plnkr.co/edit/9p5Y6H?p=preview

Exemples:

1.234,56  => 1234.56
1,234.56USD => 1234.56
1,234,567 => 1234567
1.234.567 => 1234567
1,234.567 => 1234.567
1.234 => 1234 // might be wrong - best guess
1,234 => 1234 // might be wrong - best guess
1.2345 => 1.2345
0,123 => 0.123

La fonction a un point faible: il n'est pas possible de deviner le format si vous avez 1,123 ou 1,123 - parce que selon le format local, les deux peuvent être une virgule ou un séparateur de milliers. Dans ce cas particulier, la fonction traitera le séparateur comme un séparateur de milliers et retournera 1123.

Gerfried
la source
Il échoue pour des nombres comme 1,111,11, qui est évidemment au format anglais, mais renvoie 111111
Mr. Goferito
Merci, M. Goferito - je suis désolé - j'ai réparé la fonction.
Gerfried
On dirait que cela peut également échouer pour de très petits nombres "0,124" dans les paramètres régionaux français par exemple.
Paul Alexander
Wou, super! C'est presque exactement ce que je cherchais: 3,00 €il est également remplacé par 3.00. Juste une note, qui 3,001est formatée en 3001. Pour éviter cela, l'entrée doit toujours être avec des symboles décimaux. Par exemple, 3,001.00€ 3,001.00convertit correctement. Veuillez également mettre à jour jsfiddle. 0,124il est encore converti en 124
Arnis Juraga
bravo mon pote, je pense que cela vaut la peine d'être la bonne réponse
Waheed
5

Il est déconcertant qu'ils aient inclus un toLocaleString mais pas une méthode d' analyse . Au moins toLocaleString sans arguments est bien pris en charge dans IE6 +.

Pour une solution i18n , j'ai proposé ceci:

Commencez par détecter le séparateur décimal des paramètres régionaux de l'utilisateur:

var decimalSeparator = 1.1;
decimalSeparator = decimalSeparator.toLocaleString().substring(1, 2);

Puis normalisez le nombre s'il y a plus d'un séparateur décimal dans la chaîne:

var pattern = "([" + decimalSeparator + "])(?=.*\\1)";separator
var formatted = valor.replace(new RegExp(pattern, "g"), "");

Enfin, supprimez tout ce qui n'est pas un nombre ou un séparateur décimal:

formatted = formatted.replace(new RegExp("[^0-9" + decimalSeparator + "]", "g"), '');
return Number(formatted.replace(decimalSeparator, "."));
Fábio
la source
5

Il s'agit d'un wrapper simpliste et discret autour de la parseFloatfonction.

function parseLocaleNumber(str) {
  // Detect the user's locale decimal separator:
  var decimalSeparator = (1.1).toLocaleString().substring(1, 2);
  // Detect the user's locale thousand separator:
  var thousandSeparator = (1000).toLocaleString().substring(1, 2);
  // In case there are locales that don't use a thousand separator
  if (thousandSeparator.match(/\d/))
    thousandSeparator = '';

  str = str
    .replace(new RegExp(thousandSeparator, 'g'), '')
    .replace(new RegExp(decimalSeparator), '.')

  return parseFloat(str);
}
Adam Jagosz
la source
2

Si vous voulez éviter le problème que David Meister a posté et que vous êtes sûr du nombre de décimales, vous pouvez remplacer tous les points et virgules et diviser par 100, ex .:

var value = "2,299.00";
var amount = parseFloat(value.replace(/"|\,|\./g, ''))/100;

ou si vous avez 3 décimales

var value = "2,299.001";
var amount = parseFloat(value.replace(/"|\,|\./g, ''))/1000;

C'est à vous de décider si vous souhaitez utiliser parseInt, parseFloat ou Number. Aussi, si vous souhaitez conserver le nombre de décimales, vous pouvez utiliser la fonction .toFixed (...).

Fernando Limeira
la source
1

Toutes ces réponses échouent si vous avez un nombre de millions.

3 456 789 renverrait simplement 3456 avec la méthode de remplacement.

La réponse la plus correcte pour simplement supprimer les virgules devrait être.

var number = '3,456,789.12';
number.split(',').join('');
/* number now equips 3456789.12 */
parseFloat(number);

Ou simplement écrit.

number = parseFloat(number.split(',').join(''));
Cas
la source
Bien sûr, les Américains utilisent la virgule et non le point. Il serait insensé d'essayer de faire une monstruosité pour tenter de gérer les deux.
Affaire
2
mais une telle "monstruosité" existe déjà dans le cadre de ce que fournit Unicode (voir ma réponse). Je suis sûr que vous vous sentiriez moins stupide à ce sujet si vous dirigiez une entreprise avec des clients internationaux.
David Meister
1

Cela convertit un nombre de n'importe quel endroit en nombre normal. Fonctionne aussi pour les points décimaux:

function numberFromLocaleString(stringValue, locale){
    var parts = Number(1111.11).toLocaleString(locale).replace(/\d+/g,'').split('');
    if (stringValue === null)
        return null;
    if (parts.length==1) {
        parts.unshift('');
    }   
    return Number(String(stringValue).replace(new RegExp(parts[0].replace(/\s/g,' '),'g'), '').replace(parts[1],"."));
}
//Use default browser locale
numberFromLocaleString("1,223,333.567") //1223333.567

//Use specific locale
numberFromLocaleString("1 223 333,567", "ru") //1223333.567
Eldar Gerfanov
la source
1

Avec cette fonction, vous serez en mesure de formater des valeurs dans plusieurs formats comme 1.234,56et 1,234.56, et même avec des erreurs comme 1.234.56et1,234,56

/**
 * @param {string} value: value to convert
 * @param {bool} coerce: force float return or NaN
 */
function parseFloatFromString(value, coerce) {
    value = String(value).trim();

    if ('' === value) {
        return value;
    }

    // check if the string can be converted to float as-is
    var parsed = parseFloat(value);
    if (String(parsed) === value) {
        return fixDecimals(parsed, 2);
    }

    // replace arabic numbers by latin
    value = value
    // arabic
    .replace(/[\u0660-\u0669]/g, function(d) {
        return d.charCodeAt(0) - 1632;
    })

    // persian
    .replace(/[\u06F0-\u06F9]/g, function(d) {
        return d.charCodeAt(0) - 1776;
    });

    // remove all non-digit characters
    var split = value.split(/[^\dE-]+/);

    if (1 === split.length) {
        // there's no decimal part
        return fixDecimals(parseFloat(value), 2);
    }

    for (var i = 0; i < split.length; i++) {
        if ('' === split[i]) {
            return coerce ? fixDecimals(parseFloat(0), 2) : NaN;
        }
    }

    // use the last part as decimal
    var decimal = split.pop();

    // reconstruct the number using dot as decimal separator
    return fixDecimals(parseFloat(split.join('') +  '.' + decimal), 2);
}

function fixDecimals(num, precision) {
    return (Math.floor(num * 100) / 100).toFixed(precision);
}
parseFloatFromString('1.234,56')
"1234.56"
parseFloatFromString('1,234.56')
"1234.56"
parseFloatFromString('1.234.56')
"1234.56"
parseFloatFromString('1,234,56')
"1234.56"
joseantgv
la source
0

Si vous voulez une réponse l10n, faites-le de cette façon. L'exemple utilise la devise, mais vous n'en avez pas besoin. La bibliothèque Intl devra être polyfilled si vous devez prendre en charge des navigateurs plus anciens.

var value = "2,299.00";
var currencyId = "USD";
var nf = new Intl.NumberFormat(undefined, {style:'currency', currency: currencyId, minimumFractionDigits: 2});

value = nf.format(value.replace(/,/g, ""));
Tony Topper
la source
9
ce n'est pas vraiment la réponse l10n, car pour certains paramètres régionaux (par exemple DE), la virgule est le point décimal.
Andreas
Et replace()remplace également la première correspondance lorsque vous ne l'utilisez pas RegExpavec un 'g'indicateur.
binki
@binki Merci. Fixé.
Tony Topper
@Andreas C'est la réponse l10n. Si vous vouliez une autre devise, vous changiez simplement la valeur de currencyId en deviseId de ce pays.
Tony Topper
1
@TonyTopper cela ne change pas le fait que le remplacement du séparateur des milliers est codé en dur, ,ce qui est dans un séparateur de virgule dans certains paramètres régionaux
Andreas
0

Remplacez la virgule par une chaîne vide:

var x = parseFloat("2,299.00".replace(",",""))
alert(x);

Aadit M Shah
la source
ce n'est pas sûr. Comme mentionné dans d'autres réponses, dans de nombreuses régions du monde, les virgules représentent un point décimal.
David Meister
1
De plus, cette méthode ne fonctionnerait pas sur 2,299,000.00 car elle ne remplacerait que la première virgule
wizulus
0

Si vous avez un petit ensemble de paramètres régionaux à prendre en charge, vous feriez probablement mieux de simplement coder en dur quelques règles simples:

function parseNumber(str, locale) {
  let radix = ',';
  if (locale.match(/(en|th)([-_].+)?/)) {
    radix = '.';
  }
  return Number(str
    .replace(new RegExp('[^\\d\\' + radix + ']', 'g'), '')
    .replace(radix, '.'));
}
Andreas Baumgart
la source
0
const parseLocaleNumber = strNum => {
    const decSep = (1.1).toLocaleString().substring(1, 2);
    const formatted = strNum
        .replace(new RegExp(`([${decSep}])(?=.*\\1)`, 'g'), '')
        .replace(new RegExp(`[^0-9${decSep}]`, 'g'), '');
    return Number(formatted.replace(decSep, '.'));
};
Denys Rusov
la source
0
Number("2,299.00".split(',').join(''));   // 2299

La fonction split divise la chaîne en un tableau en utilisant "," comme séparateur et renvoie un tableau.
La fonction de jointure joint les éléments du tableau renvoyés par la fonction de fractionnement.
La fonction Number () convertit la chaîne jointe en un nombre.

Bruno
la source
Veuillez expliquer un peu plus quelle est la solution et comment résout le problème.
Tariq