Convert String with Dot or Comma as decimal separator to number in JavaScript
Asked Answered
M

12

74

An input element contains numbers a where comma or dot is used as decimal separator and space may be used to group thousands like this:

'1,2'
'110 000,23'
'100 1.23'

How would one convert them to a float number in the browser using JavaScript?

jQuery and jQuery UI are used. Number(string) returns NaN and parseFloat() stops on first space or comma.

Melanoid answered 15/9, 2011 at 13:42 Comment(1)
Future readers, please note that nearly every single replace should instead be replaceAll in the answers below!Suanne
B
59

Do a replace first:

parseFloat(str.replace(',','.').replace(' ',''))
Bahia answered 15/9, 2011 at 13:45 Comment(7)
This completely ignores the fact that , is often used as a thousands separator (grouping). If you use this on a number like 1,000,000.00 you get 1, which is obviously completely wrong.Nonsmoker
@Thor84no You suppose his code will run in EN culture, but if the data will be input from people using i.e. French, Danish, Finnish, Czech etc. language, there will be no confusion. It all depends on the use of the codeCrimpy
@Crimpy I'm not supposing that at all. Most if not all languages/regions use digit grouping of some description. The character used varies (e.g. [ .,']). I'm pointing out the fact that this code is ignoring a huge amount of inputs that will make it incorrect with no warning in the answer (and no way to know it has happened). Even if the languages you list won't have this issue that does not negate the fact that in many many languages it will have that issue. Note that it will be equally broken if the language in question uses . as its digit grouping character.Nonsmoker
This is the worst solution ever I have come accross at SO, ever! SO is community based and ignores the fact that a solution, though it works for the OP, usually is not the right answer. If I used this solution in a financial software, I would be in prison or be killed by now..Felafel
Please dont use it.Fructify
I think this is a decent answer. I hate it when users try to use corner cases to try and ruin a good answer that worked for someone. If your environment and customer can guarantee, for example, that the culture is EN or that no financial data will be converted like this, what's the big deal? Is code only "acceptable" if it's an over-engineered Swiss army knife? Is this really that much of a bad piece of code that someone would get killed over it, @KemalEmin?Consignor
@Consignor it would be a decent answer if it was up-front and clearly highlighted the limitations of it rather than represent itself as a simple solution with no caveats. The fact that when it fails, it fails silently and spectacularly should warrant at least a warning to people who are looking for a solution similar to what the original question was asking. SO is about providing solutions to more than just the person who asked the question in exclusively the situation they described.Nonsmoker
N
33

I realise I'm late to the party, but I wanted a solution for this that properly handled digit grouping as well as different decimal separators for currencies. As none of these fully covered my use case I wrote my own solution which may be useful to others:

function parsePotentiallyGroupedFloat(stringValue) {
    stringValue = stringValue.trim();
    var result = stringValue.replace(/[^0-9]/g, '');
    if (/[,\.]\d{2}$/.test(stringValue)) {
        result = result.replace(/(\d{2})$/, '.$1');
    }
    return parseFloat(result);
}

This should strip out any non-digits and then check whether there was a decimal point (or comma) followed by two digits and insert the decimal point if needed.

It's worth noting that I aimed this specifically for currency and as such it assumes either no decimal places or exactly two. It's pretty hard to be sure about whether the first potential decimal point encountered is a decimal point or a digit grouping character (e.g., 1.542 could be 1542) unless you know the specifics of the current locale, but it should be easy enough to tailor this to your specific use case by changing \d{2}$ to something that will appropriately match what you expect to be after the decimal point.

Nonsmoker answered 30/3, 2015 at 13:10 Comment(2)
For supporting negative values, add \- (scaped minus) after 9 in regex. It should be like var result = stringValue.replace(/[^0-9\-]/g, '');Baudoin
You don't actually have to escape the minus, but yeah, that works.Nonsmoker
S
22

The perfect solution

accounting.js is a tiny JavaScript library for number, money and currency formatting.

Check this for ref

Senior answered 13/2, 2013 at 5:33 Comment(2)
Thanks. This doesn't provide an option to check for improper formatting when doing unformat, but it is a good base library to build that functionality on top of. Example of ambiguous result: jsfiddle.net/SkzYeTransposition
For future reference, this library has moved to openexchangerates.github.io/accounting.jsNonsmoker
F
9

You could replace all spaces by an empty string, all comas by dots and then parse it.

var str = "110 000,23";
var num = parseFloat(str.replace(/\s/g, "").replace(",", "."));
console.log(num);

I used a regex in the first one to be able to match all spaces, not just the first one.

Frazier answered 15/9, 2011 at 13:47 Comment(0)
U
7

This is the best solution

http://numeraljs.com/

numeral().unformat('0.02'); = 0.02
Uncharted answered 28/7, 2015 at 17:31 Comment(0)
C
6

What about:

parseFloat(str.replace(' ', '').replace('.', '').replace(',', '.'));
Claudianus answered 8/12, 2016 at 16:43 Comment(0)
I
6

All the other solutions require you to know the format in advance. I needed to detect(!) the format in every case and this is what I end up with.

function detectFloat(source) {
    let float = accounting.unformat(source);
    let posComma = source.indexOf(',');
    if (posComma > -1) {
        let posDot = source.indexOf('.');
        if (posDot > -1 && posComma > posDot) {
            let germanFloat = accounting.unformat(source, ',');
            if (Math.abs(germanFloat) > Math.abs(float)) {
                float = germanFloat;
            }
        } else {
            // source = source.replace(/,/g, '.');
            float = accounting.unformat(source, ',');
        }
    }
    return float;
}

This was tested with the following cases:

        const cases = {
            "0": 0,
            "10.12": 10.12,
            "222.20": 222.20,
            "-222.20": -222.20,
            "+222,20": 222.20,
            "-222,20": -222.20,
            "-2.222,20": -2222.20,
            "-11.111,20": -11111.20,
        };

Suggestions welcome.

Intercept answered 19/4, 2017 at 10:19 Comment(1)
This is a pretty nice idea, if it could be done without the use of account.js it would be a nice standalone solution.Scheel
E
5

Here's a self-sufficient JS function that solves this (and other) problems for most European/US locales (primarily between US/German/Swedish number chunking and formatting ... as in the OP). I think it's an improvement on (and inspired by) Slawa's solution, and has no dependencies.

function realParseFloat(s)
{
    s = s.replace(/[^\d,.-]/g, ''); // strip everything except numbers, dots, commas and negative sign
    if (navigator.language.substring(0, 2) !== "de" && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(s)) // if not in German locale and matches #,###.######
    {
        s = s.replace(/,/g, ''); // strip out commas
        return parseFloat(s); // convert to number
    }
    else if (/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(s)) // either in German locale or not match #,###.###### and now matches #.###,########
    {
        s = s.replace(/\./g, ''); // strip out dots
        s = s.replace(/,/g, '.'); // replace comma with dot
        return parseFloat(s);
    }
    else // try #,###.###### anyway
    {
        s = s.replace(/,/g, ''); // strip out commas
        return parseFloat(s); // convert to number
    }
}
Enlarge answered 30/10, 2019 at 18:31 Comment(7)
perfect solution!Potted
123.456,789 (as A German point of view) returns 123.456789 instead of 123456.789 thoughPotted
@Potted thanks for your comments. Are you sure? When I run this with "123.456,789" I get 123456.789. "123,456.789" will not match the first condition regex, but it does match the second condition regex, so should get processed into 123456.789.Enlarge
sorry I meant: (German) 123,456.789 turns to 123456.789 instead of expected 123.456789 and (English) 123.456,789 turns to 123456.789 instead of exptected 123.456789Potted
@Revo, thank you. One thing that is not clear is whether (in German), that the decimal places also get separated with the "." too, because in every other system they do not get separated. I have had great difficulty finding any information on formal definition of that on the internet (the Microsoft official localisation in Windows doesn't specify that it does get separated).Enlarge
@Potted How does decimal places separation work in German? so is (English) 1.2345 = (German) 1,234.5? and (English) 1.2345678 = (German) 1,234.567.8? This seems quite confusing to me (as English). I thought all decimal places, regardless of English or German has no separators. This only matters for more then 3 decimal places I guess, so for e.g. currency numbers realParseFloat still works fine.Enlarge
niceblue you made a fair point about the decimal handling of Revo. Never use anything to separate decimals. So @Revo, please reconsider what format you use because that is really not how long decimals are handled. Not even in Germany ;)Sonde
L
5

Here is my solution that doesn't have any dependencies:

return value
  .replace(/[^\d\-.,]/g, "")   // Basic sanitization. Allows '-' for negative numbers
  .replace(/,/g, ".")          // Change all commas to periods
  .replace(/\.(?=.*\.)/g, ""); // Remove all periods except the last one

(I left out the conversion to a number - that's probably just a parseFloat call if you don't care about JavaScript's precision problems with floats.)

The code assumes that:

  • Only commas and periods are used as decimal separators. (I'm not sure if locales exist that use other ones.)
  • The decimal part of the string does not use any separators.
Lillianalillie answered 8/5, 2020 at 12:57 Comment(0)
A
1

try this...

var withComma = "23,3";
var withFloat = "23.3";

var compareValue = function(str){
  var fixed = parseFloat(str.replace(',','.'))
  if(fixed > 0){
      console.log(true)
    }else{
      console.log(false);
  }
}
compareValue(withComma);
compareValue(withFloat);
Abomination answered 7/5, 2020 at 19:42 Comment(0)
C
0

This answer accepts some edge cases that others don't:

  • Only thousand separator: 1.000.000 => 1000000
  • Exponentials: 1.000e3 => 1000e3 (1 million)

Run the code snippet to see all the test suite.

const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINNING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g

export function formatNumber(number) {
  // Handle exponentials
  if ((number.match(/e/g) ?? []).length === 1) {
    const numberParts = number.split('e')
    return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
  }

  const sanitizedNumber = number
    .replace(REGEX_UNWANTED_CHARACTERS, '')
    .replace(REGEX_DASHES_EXEPT_BEGINING, '')

  // Handle only thousands separator
  if (
    ((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
    ((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
  ) {
    return sanitizedNumber.replace(/[.,]/g, '')
  }

  return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}

function formatNumberToNumber(number) {
  return Number(formatNumber(number))
}

const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g

function formatNumber(number) {
  if ((number.match(/e/g) ?? []).length === 1) {
    const numberParts = number.split('e')
    return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
  }

  const sanitizedNumber = number
    .replace(REGEX_UNWANTED_CHARACTERS, '')
    .replace(REGEX_DASHES_EXEPT_BEGINING, '')

  if (
    ((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
    ((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
  ) {
    return sanitizedNumber.replace(/[.,]/g, '')
  }

  return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}

const testCases = [
  '1',
  '1.',
  '1,',
  '1.5',
  '1,5',
  '1,000.5',
  '1.000,5',
  '1,000,000.5',
  '1.000.000,5',
  '1,000,000',
  '1.000.000',

  '-1',
  '-1.',
  '-1,',
  '-1.5',
  '-1,5',
  '-1,000.5',
  '-1.000,5',
  '-1,000,000.5',
  '-1.000.000,5',
  '-1,000,000',
  '-1.000.000',

  '1e3',
  '1e-3',
  '1e',
  '-1e',
  '1.000e3',
  '1,000e-3',
  '1.000,5e3',
  '1,000.5e-3',
  '1.000,5e1.000,5',
  '1,000.5e-1,000.5',

  '',
  'a',
  'a1',
  'a-1',
  '1a',
  '-1a',
  '1a1',
  '1a-1',
  '1-',
  '-',
  '1-1'
]

document.getElementById('tbody').innerHTML = testCases.reduce((total, input) => {
  return `${total}<tr><td>${input}</td><td>${formatNumber(input)}</td></tr>`
}, '')
<table>
  <thead><tr><th>Input</th><th>Output</th></tr></thead>
  <tbody id="tbody"></tbody>
</table>
Confirmed answered 20/1, 2021 at 15:34 Comment(0)
J
-1

From number to currency string is easy through Number.prototype.toLocaleString. However the reverse seems to be a common problem. The thousands separator and decimal point may not be obtained in the JS standard.

In this particular question the thousands separator is a white space " " but in many cases it can be a period "." and decimal point can be a comma ",". Such as in 1 000 000,00 or 1.000.000,00. Then this is how i convert it into a proper floating point number.

var price = "1 000.000,99",
    value = +price.replace(/(\.|\s)|(\,)/g,(m,p1,p2) => p1 ? "" : ".");
console.log(value);

So the replacer callback takes "1.000.000,00" and converts it into "1000000.00". After that + in the front of the resulting string coerces it into a number.

This function is actually quite handy. For instance if you replace the p1 = "" part with p1 = "," in the callback function, an input of 1.000.000,00 would result 1,000,000.00

Julijulia answered 6/10, 2016 at 12:27 Comment(2)
Both "1 000 000,99" and "1 000 000.99" result in NaN. Your syntax also kind of obfuscates that p1 = and p2 = actually do nothing. The values assigned to them are returned because it's a single statement arrow function, but the assignment is completely unnecessary and just makes it harder to read.Nonsmoker
@Thor84no Thanks for heads up.Julijulia

© 2022 - 2024 — McMap. All rights reserved.