Como posso analisar uma string com um separador de milhar de vírgula para um número?

104

Eu tenho 2,299.00uma string e estou tentando analisá-la em um número. Tentei usar parseFloat, que resultou em 2. Acho que o problema é a vírgula, mas como resolveria esse problema da maneira certa? Basta remover a vírgula?

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

user1540714
fonte

Respostas:

121

Sim, remova as vírgulas:

parseFloat(yournumber.replace(/,/g, ''));
Sam
fonte
7
Sim, mas agora as casas decimais foram perdidas. 2300,00 resulta em 2300, por exemplo.
user1540714
@ user1540714 isso porque é um float ao invés de uma string. Se você precisar gerá-la, formate-a para mostrar sempre 2 casas decimais.
Jon Taylor
Isso pode ser evitado? Tentarei
Corrigido
38
Posso sugerir que, em vez disso, capturemos todas as vírgulas usando: .replace(/,/g, '') Este regex usa o global gpara substituir tudo.
gdibble
32
Não tenho ideia de por que essa resposta recebeu tantos votos positivos e foi selecionada como correta. Ela tem dois problemas sérios. 1. Ele não consegue lidar com números com várias vírgulas, por exemplo, parseFloat("2,000,000.00".replace(',',''))retorna 2000e 2. falha em muitas regiões do mundo onde ,é uma casa decimal, por exemplo, parseFloat("2.000.000,00".replace(',',''))retorna 2- veja minha resposta abaixo para algo que funciona em qualquer lugar do mundo.
David Meister
113

Remover vírgulas é potencialmente perigoso porque, como outros mencionaram nos comentários, muitas localidades usam vírgulas para significar algo diferente (como uma casa decimal).

Eu não sei de onde você tirou sua corda, mas em alguns lugares do mundo "2,299.00"=2.299

O Intlobjeto poderia ter sido uma boa maneira de resolver este problema, mas de alguma forma eles conseguiram enviar a especificação com apenas uma Intl.NumberFormat.format()API e nenhuma parsecontraparte :(

A única maneira de analisar uma string com caracteres numéricos culturais em um número reconhecível por máquina de qualquer maneira sã é usar uma biblioteca que aproveita os dados CLDR para cobrir todas as maneiras possíveis de formatar strings numéricas http: //cldr.unicode. org /

As duas melhores opções de JS que encontrei para isso até agora:

David Meister
fonte
4
Não posso acreditar que ninguém votou a favor desta resposta ainda, é a única resposta real nesta página!
evilkos
9
Concordo totalmente que o Intl deveria ter uma contraparte de análise. Parece óbvio que as pessoas precisariam disso.
carlossless
Esta é a única maneira sistemática de fazer isso. Não é possível conseguir isso com uma abordagem de regex que serve para todos. Vírgulas e pontos têm significados diferentes em idiomas diferentes.
Wildhammer
38

Em navegadores modernos, você pode usar o Intl.NumberFormat integrado para detectar a formatação de número do navegador e normalizar a entrada para corresponder.

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

Obviamente há espaço para alguma otimização e armazenamento em cache, mas isso funciona de maneira confiável em todas as linguagens.

Paul Alexander
fonte
Por que não recebeu tantos votos positivos !!! É a solução mais elegante para INPUT em formatos internacionais! Obrigado!!
kpollock
na França, a moeda é 231 123 413,12
stackdave
26

Remova tudo o que não seja um dígito, ponto decimal ou sinal de menos ( -):

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

Violino atualizado

Observe que isso não funciona para números em notação científica. Se você deseja que ele, altere a replacelinha para adicionar e, Ee +para a lista de caracteres aceitáveis:

str = str.replace(/[^\d\.\-eE+]/g, "");
TJ Crowder
fonte
4
Isso não funcionará para números negativos ou números em notação científica.
Aadit M Shah
1
str = str.replace(/(\d+),(?=\d{3}(\D|$))/g, "$1"); Isso é o que eu usaria, mas sou totalmente inútil em regex e é algo que encontrei há algum tempo em algum outro segmento do SO.
Jon Taylor
@JonTaylor: O objetivo aqui não era validar o número, apenas fazer com que funcionasse parseFloat- o que irá validá-lo. :-)
TJ Crowder
o que o meu faz, tanto quanto eu sei. Eu uso isso para converter 1.234.567 etc para 1234567. Como eu disse, embora eu seja totalmente inútil em regex, então eu não poderia dizer a vocês o que isso realmente faz lol.
Jon Taylor
1
má ideia remover o sinal de menos "-" - obter outro número completo, e também se você analisar formatos heterogêneos, "7.500"! == "7.500"
Serge,
14

Normalmente, você deve considerar o uso de campos de entrada que não permitem a entrada de texto livre para valores numéricos. Mas pode haver casos em que você precisa adivinhar o formato de entrada. Por exemplo 1.234,56 na Alemanha significa 1.234,56 nos EUA. Consulte https://salesforce.stackexchange.com/a/21404 para obter uma lista de países que usam vírgula como decimal.

Eu uso a seguinte função para fazer uma melhor estimativa e remover todos os caracteres não numéricos:

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

Experimente aqui: https://plnkr.co/edit/9p5Y6H?p=preview

Exemplos:

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

A função tem um ponto fraco: não é possível adivinhar o formato se você tiver 1.123 ou 1.123 - porque dependendo do formato do local, ambos podem ser uma vírgula ou um separador de milhares. Neste caso especial, a função tratará o separador como um separador de milhares e retornará 1123.

Gerfried
fonte
Ele falha para números como 1.111,11, que obviamente é o formato inglês, mas retorna
111111
Obrigado, Sr. Goferito - desculpe - consertei a função.
Gerfried,
Parece que isso também pode falhar para números muito pequenos "0,124" na localidade francesa, por exemplo.
Paul Alexander
Uau, ótimo! Isso é quase exatamente o que eu estava procurando: 3,00 €também foi substituído para 3,00. Apenas uma nota 3,001formatada para 3001. Para evitar isso, a entrada deve ser sempre com símbolos decimais. Por exemplo, 3,001.00€ 3,001.00converte corretamente. Além disso, atualize o jsfiddle. 0,124ainda é convertido para 124
Arnis Juraga
bem feito amigo, acho que vale a pena ser a resposta correta
Waheed
5

É desconcertante que incluíram um toLocaleString, mas não um método de análise . Pelo menos toLocaleString sem argumentos é bem suportado no IE6 +.

Para uma solução i18n , pensei nisso:

Primeiro detecte o separador decimal de localidade do usuário:

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

Em seguida, normalize o número se houver mais de um separador decimal na string:

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

Por fim, remova tudo o que não seja um número ou separador decimal:

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

Este é um invólucro simples e discreto em torno da parseFloatfunção.

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
fonte
2

Se você quiser evitar o problema que David Meister postou e tiver certeza do número de casas decimais, pode substituir todos os pontos e vírgulas e dividir por 100, ex .:

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

ou se você tiver 3 casas decimais

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

Você decide se deseja usar parseInt, parseFloat ou Number. Além disso, se você quiser manter o número de casas decimais, pode usar a função .toFixed (...).

Fernando Limeira
fonte
1

Todas essas respostas falharão se você tiver um número na casa dos milhões.

3.456.789 simplesmente retornaria 3456 com o método de substituição.

A resposta mais correta para simplesmente remover as vírgulas teria que ser.

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

Ou simplesmente escrito.

number = parseFloat(number.split(',').join(''));
Caso
fonte
Claro, os americanos usam vírgula e não ponto. Seria tolice tentar fazer uma monstruosidade para tentar lidar com ambos.
Caso
2
mas essa "monstruosidade" já existe como parte do que o Unicode fornece (veja minha resposta). Tenho certeza de que você se sentiria menos tolo sobre isso se dirigisse uma empresa com clientes internacionais.
David Meister
1

Isso converte um número em qualquer local para um número normal. Funciona para pontos decimais também:

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
fonte
1

Com esta função você poderá formatar valores em vários formatos como 1.234,56e 1,234.56, e até mesmo com erros como 1.234.56e1,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
fonte
0

Se você quer uma resposta l10n, faça desta forma. O exemplo usa moeda, mas você não precisa disso. A biblioteca internacional precisará ser polyfilled se você tiver que oferecer suporte a navegadores mais antigos.

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
fonte
9
esta não é realmente uma resposta l10n, porque para alguns locais (por exemplo, DE) a vírgula é o ponto decimal.
Andreas de
E também replace()apenas substitui a primeira correspondência quando não é usado RegExpcom o 'g'sinalizador.
binki
@binki Obrigado. Fixo.
Tony Topper
@Andreas Esta é a resposta l10n. Se você quiser outra moeda, basta alterar o valor de currencyId para currencyId desse país.
Tony Topper
1
@TonyTopper isso não muda o fato de que a substituição do separador de milhares é codificada para o ,que está em separador de vírgula em alguns locais
Andreas
0

Substitua a vírgula por uma string vazia:

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

Aadit M Shah
fonte
isso não é seguro. Conforme mencionado em outras respostas, em muitas partes do mundo, as vírgulas representam um ponto decimal.
David Meister
1
Além disso, este método não funcionaria em 2.299.000,00, pois apenas substituiria a primeira vírgula
wizulus
0

Se você tiver um pequeno conjunto de localidades para oferecer suporte, provavelmente seria melhor apenas codificar algumas regras 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
fonte
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
fonte
0
Number("2,299.00".split(',').join(''));   // 2299

A função de divisão divide a string em uma matriz usando "," como separador e retorna uma matriz.
A função de junção junta os elementos da matriz retornada da função de divisão.
A função Number () converte a string associada em um número.

Bruno
fonte
Elabore um pouco mais sobre qual é a solução e como ela resolve o problema.
Tariq