Jak mogę przeanalizować ciąg z przecinkiem separatora tysięcy na liczbę?

104

Mam 2,299.00jako ciąg i próbuję przeanalizować go na liczbę. Próbowałem użyć parseFloat, co daje 2. Myślę, że problem stanowi przecinek, ale jak mam rozwiązać ten problem we właściwy sposób? Po prostu usunąć przecinek?

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

user1540714
źródło

Odpowiedzi:

121

Tak, usuń przecinki:

parseFloat(yournumber.replace(/,/g, ''));
Sam
źródło
7
Tak, ale teraz miejsca po przecinku są stracone. Na przykład 2300,00 daje 2300.
user1540714
@ user1540714 to dlatego, że jest to liczba zmiennoprzecinkowa, a nie ciąg znaków. Jeśli chcesz go następnie wyprowadzić, musisz go sformatować, aby zawsze pokazywał 2 miejsca dziesiętne.
Jon Taylor
Czy można tego uniknąć? Spróbuję naprawić
user1540714
38
Mogę zasugerować, abyśmy zamiast tego przechwycili wszystkie przecinki za pomocą: .replace(/,/g, '') To wyrażenie regularne używa globalnego gdo zastąpienia wszystkiego.
gdibble
32
Nie mam pojęcia, dlaczego ta odpowiedź otrzymała tak wiele głosów za i została wybrana jako poprawna, ma dwa poważne problemy. 1. Nie radzi sobie z liczbami z wieloma przecinkami, np. parseFloat("2,000,000.00".replace(',',''))Zwraca 2000i 2. zawodzi w wielu regionach świata, w których ,występuje miejsce po przecinku, np. parseFloat("2.000.000,00".replace(',',''))Zwraca 2- zobacz moją odpowiedź poniżej na coś, co działa na całym świecie.
David Meister
113

Usuwanie przecinków jest potencjalnie niebezpieczne, ponieważ, jak wspominali inni w komentarzach, w wielu lokalizacjach przecinek oznacza coś innego (np. Miejsce dziesiętne).

Nie wiem, skąd masz swój ciąg, ale w niektórych miejscach na świecie "2,299.00"=2.299

IntlPrzedmiot mógł być dobry sposób, aby rozwiązać ten problem, ale jakoś udało im się wysyłać spec tylko z Intl.NumberFormat.format()API i bez parseodpowiednika :(

Jedynym sposobem na przeanalizowanie ciągu znaków z kulturowymi znakami numerycznymi do postaci rozpoznawalnej przez maszynę liczby w jakikolwiek rozsądny sposób i18n jest użycie biblioteki, która wykorzystuje dane CLDR do pokrycia wszystkich możliwych sposobów formatowania ciągów liczbowych http: //cldr.unicode. org /

Dwie najlepsze opcje JS, z jakimi się do tej pory spotkałem:

David Meister
źródło
4
Nie mogę uwierzyć, że nikt jeszcze nie zagłosował za tą odpowiedzią, to jedyna rzeczywista odpowiedź na tej stronie!
evilkos
9
Całkowicie się zgadzam, że Intl powinien mieć odpowiednik parsujący. Wydaje się oczywiste, że ludzie będą tego potrzebować.
karola,
To jedyny systematyczny sposób, aby to zrobić. Nie można tego osiągnąć za pomocą jednego wyrażenia regularnego, które pasuje do wszystkich. Przecinki i kropki mają różne znaczenie w różnych językach.
Wildhammer
38

W nowoczesnych przeglądarkach możesz użyć wbudowanego Intl.NumberFormat, aby wykryć formatowanie liczb w przeglądarce i znormalizować dane wejściowe, aby pasowały.

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

Jest oczywiście miejsce na optymalizację i buforowanie, ale działa to niezawodnie we wszystkich językach.

Paul Alexander
źródło
Dlaczego to nie ma wielu pozytywnych głosów !!! To najbardziej eleganckie rozwiązanie INPUT w formatach międzynarodowych! Dziękuję Ci!!
kpollock
we Francji walutą jest 231 123
413,12
26

Usuń wszystko, co nie jest cyfrą, kropką dziesiętną ani znakiem minus ( -):

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

Zaktualizowane skrzypce

Zauważ, że nie będzie działać dla liczb w notacji naukowej. Jeśli chcesz go zmienić replacelinię dodać e, Ei +do listy dopuszczalnych znaków:

str = str.replace(/[^\d\.\-eE+]/g, "");
TJ Crowder
źródło
4
To nie zadziała w przypadku liczb ujemnych lub liczb w notacji naukowej.
Aadit M Shah
1
str = str.replace(/(\d+),(?=\d{3}(\D|$))/g, "$1"); To jest to, czego bym użył, ale jestem całkowicie bezużyteczny w regex i jest to coś, co znalazłem jakiś czas temu w innym wątku SO.
Jon Taylor
@JonTaylor: Celem tutaj nie było zweryfikowanie numeru, tylko sprawienie, by działał parseFloat- co to zweryfikuje. :-)
TJ Crowder
co moje robi, o ile wiem. Używam tego do konwersji 1,234,567 itd. Na 1234567. Jak już powiedziałem, jestem całkowicie bezużyteczny w regex, więc za całe życie nie mogłem ci powiedzieć, co tak naprawdę robi lol.
Jon Taylor
1
zły pomysł, aby usunąć znak minus "-" - uzyskaj zupełnie inną liczbę, a także jeśli przeanalizujesz formaty heterogeniczne, "7.500"! == "7500"
Serge
14

Zwykle należy rozważyć użycie pól wejściowych, które nie pozwalają na dowolne wprowadzanie tekstu dla wartości liczbowych. Ale mogą być przypadki, kiedy trzeba odgadnąć format wejściowy. Na przykład 1,234,56 w Niemczech oznacza 1234,56 w USA. Wejdź na https://salesforce.stackexchange.com/a/21404, aby zapoznać się z listą krajów, w których przecinek jest dziesiętny.

Używam następującej funkcji, aby zgadnąć i usunąć wszystkie znaki nienumeryczne:

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

Wypróbuj tutaj: https://plnkr.co/edit/9p5Y6H?p=preview

Przykłady:

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

Funkcja ma jeden słaby punkt: nie można odgadnąć formatu, jeśli masz 1,123 lub 1,123 - ponieważ w zależności od formatu ustawień regionalnych oba mogą być przecinkiem lub separatorem tysięcy. W tym szczególnym przypadku funkcja potraktuje separator jako separator tysięcy i zwróci 1123.

Gerfried
źródło
Nie udaje się w przypadku liczb takich jak 1111,11, co jest oczywiście formatem angielskim, ale zwraca 111111
Mr. Goferito
Dziękuję panie Goferito - przepraszam - naprawiłem funkcję.
Gerfried
Wygląda na to, że na przykład może to się nie powieść w przypadku bardzo małych liczb „0,124” w języku francuskim.
Paul Alexander
Świetnie! To jest prawie dokładnie to, czego szukałem: 3,00 €również jest zamienione na 3,00. Tylko uwaga, która 3,001jest sformatowana na 3001. Aby tego uniknąć, dane wejściowe powinny zawsze zawierać symbole dziesiętne. Np. 3,001.00€ 3,001.00Konwertuje poprawnie. Zaktualizuj jsfiddle. 0,124nadal jest konwertowany na 124
Arnis Juraga
dobra robota kolego Myślę, że warto podać poprawną odpowiedź
Waheed
5

Zaskakujące jest to, że uwzględnili toLocaleString, ale nie metodę analizy . Przynajmniej toLocaleString bez argumentów jest dobrze obsługiwana w IE6 +.

W przypadku rozwiązania i18n wymyśliłem to:

Najpierw wykryj lokalny separator dziesiętny użytkownika:

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

Następnie znormalizuj liczbę, jeśli w ciągu znaków jest więcej niż jeden separator dziesiętny:

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

Na koniec usuń wszystko, co nie jest liczbą ani separatorem dziesiętnym:

formatted = formatted.replace(new RegExp("[^0-9" + decimalSeparator + "]", "g"), '');
return Number(formatted.replace(decimalSeparator, "."));
Fábio
źródło
5

Jest to uproszczone, dyskretne opakowanie wokół parseFloatfunkcji.

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
źródło
2

Jeśli chcesz uniknąć problemu, który opublikował David Meister i masz pewność co do liczby miejsc po przecinku, możesz zamienić wszystkie kropki i przecinki i podzielić przez 100, np .:

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

lub jeśli masz 3 miejsca po przecinku

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

To zależy od Ciebie, czy chcesz używać parseInt, parseFloat czy Number. Jeśli chcesz zachować liczbę miejsc dziesiętnych, możesz użyć funkcji .toFixed (...).

Fernando Limeira
źródło
1

Wszystkie te odpowiedzi zawodzą, jeśli masz liczbę w milionach.

3,456,789 zwróciłoby po prostu 3456 przy użyciu metody replace.

Najbardziej poprawną odpowiedzią na proste usunięcie przecinków powinno być.

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

Lub po prostu napisane.

number = parseFloat(number.split(',').join(''));
Walizka
źródło
Oczywiście amerykańskie używają przecinka, a nie kropki. Byłoby głupotą próbować zrobić potworność, aby poradzić sobie z jednym i drugim.
Sprawa
2
ale taka „potworność” już istnieje jako część tego, co zapewnia Unicode (zobacz moją odpowiedź). Jestem pewien, że czułbyś się mniej głupio, gdybyś prowadził firmę z międzynarodowymi klientami.
David Meister
1

To konwertuje liczbę w dowolnym miejscu na normalną liczbę. Działa również w przypadku miejsc dziesiętnych:

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
źródło
1

Dzięki tej funkcji będziesz mógł formatować wartości w wielu formatach, takich jak 1.234,56i 1,234.56, a nawet z błędami, takimi jak 1.234.56i1,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
źródło
0

Jeśli chcesz uzyskać odpowiedź l10n, zrób to w ten sposób. Przykład używa waluty, ale nie potrzebujesz tego. Biblioteka Intl będzie musiała zostać wypełniona, jeśli musisz obsługiwać starsze przeglądarki.

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
źródło
9
to nie jest tak naprawdę odpowiedź l10n, ponieważ dla niektórych ustawień regionalnych (np. DE) przecinek jest kropką dziesiętną.
Andreas
A także replace()zastępuje pierwsze dopasowanie tylko wtedy, gdy nie jest używane RegExpz 'g'flagą.
binki
@binki Thanks. Naprawiony.
Tony Topper,
@Andreas To jest odpowiedź l10n. Jeśli chcesz mieć inną walutę, po prostu zmienisz wartość currencyId na ten kraj currencyId.
Tony Topper,
1
@TonyTopper nie zmienia to faktu, że zamiennik dla separatora tysięcy jest zakodowany na stałe, do ,którego w niektórych lokalizacjach jest umieszczony separator przecinków
Andreas
0

Zastąp przecinek pustym ciągiem:

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

Aadit M Shah
źródło
to nie jest bezpieczne. Jak wspomniano w innych odpowiedziach, w wielu częściach świata przecinki oznaczają przecinek.
David Meister
1
Również ta metoda nie zadziała na 2299000,00, ponieważ zastąpiłaby tylko pierwszy przecinek
wizulus
0

Jeśli masz mały zestaw języków do obsługi, prawdopodobnie lepiej by było, gdybyś po prostu zakodował kilka prostych reguł:

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
źródło
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
źródło
0
Number("2,299.00".split(',').join(''));   // 2299

Funkcja split dzieli ciąg znaków na tablicę, używając znaku „,” jako separatora i zwraca tablicę.
Funkcja join łączy elementy tablicy zwrócone przez funkcję split.
Funkcja Number () konwertuje połączony ciąg na liczbę.

Bruno
źródło
Opowiedz trochę więcej o tym, jakie jest rozwiązanie i jak rozwiązuje problem.
Tariq