如何将带有逗号千位分隔符的字符串解析为数字

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

本文关键字:分隔符 千位 字符串 数字      更新时间:2024-02-21

2,299.00为字符串,我正在尝试将其解析为一个数字。我尝试使用 parseFloat ,结果为 2。我想逗号是问题所在,但是我将如何以正确的方式解决此问题?只是删除逗号?

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

是的,

删除逗号:

let output = parseFloat("2,299.00".replace(/,/g, ''));
console.log(output);

删除逗号有潜在的危险,因为正如其他人在评论中提到的,许多区域设置使用逗号来表示不同的东西(如小数位(。

我不知道你从哪里得到你的字符串,但在世界上的一些地方"2,299.00" = 2.299

Intl对象可能是解决此问题的好方法,但不知何故,他们设法仅使用Intl.NumberFormat.format() API 发布规范,而没有parse对应的:(

任何 i18n 理智的方式将包含区域性数字字符的字符串解析为机器可识别数字的唯一方法是使用利用 CLDR 数据来涵盖格式化数字字符串的所有可能方法的库 http://cldr.unicode.org/

到目前为止,我遇到的两个最好的JS选项:

  • https://github.com/google/closure-library/tree/master/closure/goog/i18n
  • https://github.com/globalizejs/globalize

在现代浏览器上,您可以使用内置的 Intl.NumberFormat 来检测浏览器的数字格式并规范化输入以匹配。

function parseNumber(value, locales = navigator.languages) {
  const example = Intl.NumberFormat(locales).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 }`);
}

他们显然有一些优化和缓存的空间,但这在所有语言中都能可靠地工作。

警告:这不适用于科学记数法中的数字(如1e3一千(。

删除不是数字、小数分隔符或减号 (-( 的任何内容(如果要允许对数字进行一元+,也可以删除+(。

如果你可以假设.是小数分隔符(它在世界很多地方都没有;继续阅读(,那可能看起来像这样:

function convertToFloat(str) {
    let body = str;
    let sign = "";
    const signMatch = /^'s*(-|'+)/.exec(str);
    // Or if you don't want to support unary +:
    // const signMatch = /^'s*(-)/.exec(str);
    if (signMatch) {
        body = str.substring(signMatch.index + 1);
        sign = signMatch[1];
    }
    const updatedBody = str.replace(/[^'d'.]/g, "");
    const num = parseFloat(sign + updatedBody);
    return num;
}

现场示例(我在数字中添加了一小部分只是为了表明它有效(:

function convertToFloat(str) {
    let body = str;
    let sign = "";
    const signMatch = /^'s*(-|'+)/.exec(str);
    // Or if you don't want to support unary +:
    // const signMatch = /^'s*(-)/.exec(str);
    if (signMatch) {
        body = str.substring(signMatch.index + 1);
        sign = signMatch[1];
    }
    const updatedBody = str.replace(/[^'d'.]/g, "");
    const num = parseFloat(sign + updatedBody);
    return num;
}
console.log(convertToFloat("2,299.23"));

如果要支持.不是小数分隔符的区域设置(有很多(,则可以检测小数分隔符并在正则表达式中使用检测到的小数分隔符。下面是查找小数分隔符的示例函数:

function findDecimalSeparator() {
    const num = 1.2;
    if (typeof Intl === "object" && Intl && Intl.NumberFormat) {
        // I'm surprised it's this much of a pain and am hoping I'm missing
        // something in the API
        const formatter = new Intl.NumberFormat();
        const parts = formatter.formatToParts(num);
        const decimal = parts.find(({ type }) => type === "decimal").value;
        return decimal;
    }
    // Doesn't support `Intl.NumberFormat`, fall back to dodgy means
    const str = num.toLocaleString();
    const parts = /1('D+)2/.exec(str);
    return parts[1];
}

然后convertToFloat看起来像:

const decimal = findDecimalSeparator();
function convertToFloat(str) {
    let body = str;
    let sign = "";
    const signMatch = /^'s*(-|'+)/.exec(str);
    // Or if you don't want to support unary +:
    // const signMatch = /^'s*(-)/.exec(str);
    if (signMatch) {
        body = str.substring(signMatch.index + 1);
        sign = signMatch[1];
    }
    const rex = new RegExp(`${escapeRegex(decimal)}|-|''+|''D`, "g");
    const updatedBody = body.replace(
        rex,
        (match) => match === decimal ? "." : ""
    );
    const num = parseFloat(sign + updatedBody);
    return num;
}

现场示例:

const decimal = findDecimalSeparator();
function findDecimalSeparator() {
    const num = 1.2;
    if (typeof Intl === "object" && Intl && Intl.NumberFormat) {
        // I'm surprised it's this much of a pain and am hoping I'm missing
        // something in the API
        const formatter = new Intl.NumberFormat();
        const parts = formatter.formatToParts(num);
        const decimal = parts.find(({ type }) => type === "decimal").value;
        return decimal;
    }
    // Doesn't support `Intl.NumberFormat`, fall back to dodgy means
    const str = num.toLocaleString();
    const parts = /1('D+)2/.exec(str);
    return parts[1];
}
function escapeRegex(string) {
    return string.replace(/[/'-''^$*+?.()|[']{}]/g, "''$&");
}
function convertToFloat(str) {
    let body = str;
    let sign = "";
    const signMatch = /^'s*(-|'+)/.exec(str);
    // Or if you don't want to support unary +:
    // const signMatch = /^'s*(-)/.exec(str);
    if (signMatch) {
        body = str.substring(signMatch.index + 1);
        sign = signMatch[1];
    }
    const rex = new RegExp(`${escapeRegex(decimal)}|-|''+|''D`, "g");
    const updatedBody = body.replace(
        rex,
        (match) => match === decimal ? "." : ""
    );
    const num = parseFloat(sign + updatedBody);
    return num;
}
function gid(id) {
    const element = document.getElementById(id);
    if (!element) {
        throw new Error(`No element found for ID ${JSON.stringify(id)}`);
    }
    return element;
}
function onClick(id, handler) {
    gid(id).addEventListener("click", handler);
}
onClick("convert", () => {
    const str = gid("num").value;
    const num = convertToFloat(str);
    console.log(`${JSON.stringify(str)} => ${num}`);
});
<div>Enter a number using your locale's grouping and decimal separators, optionally prefaced with a minus sign (<code>-</code>) or plus sign (<code>+</code>):</div>
<input type="text" id="num" value="-123">
<input type="button" id="convert" value="Convert">

通常,您应该考虑使用不允许为数值输入自由文本的输入字段。但在某些情况下,您需要猜测输入格式。例如,德国的 1.234,56 在美国意味着 1,234.56。有关使用逗号作为小数的国家/地区的列表,请参阅 https://salesforce.stackexchange.com/a/21404。

我使用以下函数进行最佳猜测并去除所有非数字字符:

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

在这里试试:https://plnkr.co/edit/9p5Y6H?p=preview

例子:

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

该函数有一个弱点:如果您有 1,123 或 1.123,则无法猜测格式 - 因为根据区域设置格式,两者都可能是逗号或千位分隔符。在这种特殊情况下,该函数会将分隔符视为千位分隔符并返回 1123。

令人

困惑的是,他们包含一个toLocaleString,但没有一个解析方法。至少没有参数的 toLocaleString 在 IE6+ 中得到了很好的支持。

对于 i18n 解决方案,我想出了这个:

首先检测用户的区域设置小数分隔符:

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

然后,如果字符串中有多个小数分隔符,则规范化数字:

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

最后,删除任何不是数字或小数分隔符的内容:

formatted = formatted.replace(new RegExp("[^0-9" + decimalSeparator + "]", "g"), '');
return Number(formatted.replace(decimalSeparator, "."));
Number("2,299.00".split(',').join(''));   // 2299

split 函数使用 ","作为分隔符将字符串拆分为数组,并返回一个数组。
join 函数联接从拆分函数返回的数组元素。
Number(( 函数将连接的字符串转换为数字。

如果你想避免David Meister发布的问题,并且你确定小数位数,你可以替换所有的点和逗号并除以100,例如:

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

或者如果您有 3 位小数

var value = "2,299.001";
var amount = parseFloat(value.replace(/"|',|'./g, ''))/1000;
如果你想

使用 parseInt、parseFloat 或 Number,这取决于你。另外,如果你想保留小数位数,你可以使用函数.toFixed(...(。

或者尝试这种较短的方法:

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

如果有多个逗号,请使用正则表达式:

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

这是我在数组中的情况(对于类似的用例(:

要获取此数组的总和,请执行以下操作:

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

通过使用parseFloat我会丢失小数,所以为了得到确切的总和,我必须首先用点替换正斜杠,然后将字符串转换为实际数字。

所以:

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

这将为我提供要在reduce方法中使用的所需数组:

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

如果你有一个数百万的数字,所有这些答案都会失败。

3,456,789 将使用替换方法简单地返回 3456。

简单地删除逗号的最正确答案必须是。

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

或者干脆写。

number = parseFloat(number.split(',').join(''));
这会将

任何区域设置中的数字转换为正常数字。也适用于小数点:

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

使用此功能,您将能够以多种格式格式化值,例如 1.234,561,234.56 ,甚至会出现1.234.561,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"

基于这里许多伟大的建筑师,我简化了它。

我更喜欢使用Intl.NumberFormat(undefined)来使其使用best fit机制。

如果用户像我一样,使用丹麦语键盘,但更喜欢 Mac 是英语,这会有所帮助: if (Number.isNaN(normalized)) return Number(value.replace(',', '.'));

如果将其用于表单,我发现我应该使用inputMode="numeric"而不是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}`);
});

如果你想要一个 l10n 的答案,那就这样做吧。 示例使用货币,但您不需要它。 如果您必须支持较旧的浏览器,则需要对 Intl 库进行填充。

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

如果您有一小组要支持的语言环境,那么硬编码几个简单的规则可能会更好:

function parseNumber(str, locale) {
  let radix = ',';
  if (locale.match(/(en|th)([-_].+)?/)) {
    radix = '.';
  }
  return Number(str
    .replace(new RegExp('[^''d''' + radix + ']', 'g'), '')
    .replace(radix, '.'));
}