How can I parse a string with a comma thousand separator to a number?

Viewed 176131

I have 2,299.00 as a string and I am trying to parse it to a number. I tried using parseFloat, which results in 2. I guess the comma is the problem, but how would I solve this issue the right way? Just remove the comma?

var x = parseFloat("2,299.00")
console.log(x);

16 Answers

It's baffling that they included a toLocaleString but not a parse method. At least toLocaleString without arguments is well supported in IE6+.

For a i18n solution, I came up with this:

First detect the user's locale decimal separator:

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

Then normalize the number if there's more than one decimal separator in the String:

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

Finally, remove anything that is not a number or a decimal separator:

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

If you want to avoid the problem that David Meister posted and you are sure about the number of decimal places, you can replace all dots and commas and divide by 100, ex.:

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

or if you have 3 decimals

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

It's up to you if you want to use parseInt, parseFloat or Number. Also If you want to keep the number of decimal places you can use the function .toFixed(...).

or try this shorter approach:

const myNum =  +('2,299.00'.replace(",",""));

If you have several commas use Regex:

const myNum =  +('2,022,233,988.55'.replace(/,/g,""));
// -> myNum = 2022233988.55

Here was my case in an array (for similar use case):

To get the sum of this array:

const numbers = ["11", "7", "15/25", "18/5", "12", "16/25"]

By using parseFloat I would lose the decimals so to get the exact sum I had to first replace the forward slash with dot, then convert the strings to actual numbers.

So:

const currectNumbers = numbers.map(num => +(num.replace("/",".")))

// or the longer approach:
const currectNumbers = numbers
.map(num => num.replace("/","."))
.map(num => parseFloat(num));

This will give me the desired array to be used in reduce method:

currectNumbers = [ 11, 7, 15.25, 18.5, 12, 16.25]

This converts a number in whatever locale to normal number. Works for decimals points too:

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
Number("2,299.00".split(',').join(''));   // 2299

The split function splits the string into an array using "," as a separator and returns an array.
The join function joins the elements of the array returned from the split function.
The Number() function converts the joined string to a number.

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, '.'));
};

With this function you will be able to format values in multiple formats like 1.234,56 and 1,234.56, and even with errors like 1.234.56 and 1,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"

Based on many great architects here, I've simplified it a bit.

I prefer to use Intl.NumberFormat(undefined) to make it use the best fit mechanism.

If the user, like me, has a Danish keyboard, but prefer the Mac to be english, this helps: if (Number.isNaN(normalized)) return Number(value.replace(',', '.'));

If this is used in a form, I found that I should use inputMode="numeric" rather than type="number".

function parseNumber(value, locales = undefined) {
  if (typeof value !== 'string') return value;
  const example = Intl.NumberFormat(locales).format('1.1');
  const normalized = Number(value.replace(example.charAt(1), '.'));
  if (Number.isNaN(normalized)) return Number(value.replace(',', '.'));
  return normalized;
}

/* test */

const tests = [
  {
    locale: 'en-US',
    candidate: 1.123,
    expected: 1.123,
  },
  {
    locale: 'en-US',
    candidate: '1.123',
    expected: 1.123,
  },
  {
    locale: 'fr-FR',
    candidate: '33.123',
    expected: 33.123,
  },
  {
    locale: 'fr-FR',
    candidate: '33,123',
    expected: 33.123,
  },
  {
    locale: 'da-DK',
    candidate: '45.123',
    expected: 45.123,
  },
  {
    locale: 'da-DK',
    candidate: '45,123',
    expected: 45.123,
  },
  {
    locale: 'en-US',
    candidate: '0.123',
    expected: 0.123,
  },
  {
    locale: undefined,
    candidate: '0,123',
    expected: 0.123,
  },
];

tests.forEach(({ locale, candidate, expected }) => {
  const parsed = parseNumber(candidate, locale);
  console.log(`${candidate} as ${typeof candidate} in ${locale}: ${parsed} === ${expected}? ${parsed === expected}`);
});

Related