货币转换插件github地址:github.com/lulu-up/goodCurrencySymbol
这是一个由我编写的轻量级数字转货币字符串插件, 因为发现各个国家的货币相关知识很有趣, 并且当前市场上相关插件比较老, 所以也想与你分享这些有趣知识。
本插件可以通过用户输入一个数字, 将其转化成货币的字符串格式, 比如说如数字12.345则转换成货币字符串:
当今世界上货币的标识方式五花八门非常有趣, 比如"印尼"的小数点是"逗号", 印尼的千分号是"句点", 新加坡的货币符号居然是"SGD", 日元的标识符号也是"¥", 既然遇到这么多奇奇怪怪的写法那当然是要把它统一起来解决啦。
我们国家使用"."(句点)作为小数点, 但是比如 '印尼'、'德国'、'巴西' 等国家都是使用","(逗号)标识小数点:
中国: 123.45
印尼: 123,45
与小数点一样, 我们国家使用","(逗号)作为千分位, '印尼'等国家使用"."(句点)标识千分位:
中国: 12,345,678
印尼: 12.345.678
我们国家比较常见是保留两位小数, 也就是精确到"元角分"的"分", 但是比如"越南盾"就是没有小数的概念, 可能是因为"越南盾"动辄几万所以小数价格几乎没有意义了:
2022/5/20: 1人民币 ≈ 3400 越南盾
为帮助大家理解可以一起看看这个新闻:
我们熟知的是 "¥" "$" 他们都只是使用一个符号来表示, 其实世界上还有好多长度大于1的标识方法:
印尼: Rp 123
新加坡: SGD 123
蒙古语: CN¥ 123
不但长度不同, 连货币符号的位置也不同, 不少国家是将货币符号放后面的:
中国 ¥ 123
德国 123 €
越南 123 ₫
冰岛 123 ISK
比如日元使用的也是 "¥":
先看下excel里面的表示方式:
可以看出默认是"负号 + 货币符号 + 金额", 但我通过网络发现不少人网站采用的是 "货币符号 + 负号 + 金额"的写法, 那么我的理解是需要给予用户自由选择的权利。
中国
-¥123
¥-123
新加坡
-SGD123
SGD-123
越南: 货币符号在后面不存在这些烦恼
-123₫
第一个是: accounting.js仓库地址(4.8k Star) 使用的人多, 不太好的点是需要用户来指定展示规则, 也就是默认翻译成美元, 其他的翻译形式与货币符号都需要用户自定义:
并且大量的issue指出这个库的计算精度有bug。 11年前的老库, 并且没有ts支持。
第二个是: currencyFormatter.js仓库地址(632 Star) 其内所有的配置全靠代码全罗列的写法不实在不优雅, 说白了就是每种语言如何展示都是内置在插件里的, 但是用户无法使用自由组合的展示方式, 比如 $123.4万
这种组合展示模式:
image.png
6年前的老库, 需要实时更新配置文件, 并且没有ts支持。
学习完上述知识后, 我们就可以在做"插件"之前进行一下需求的统计:
// 比如格式化 12345.67 为人民币, 返回的详细信息
{
isFront: true, // 货币符号是否在金额前方
currencySymbol: "¥" // 货币符号
formatValue: "12,345.67", // 货币
value: 12345.67, // 原本的值
currencyString: "¥12,345.67", // 格式化后的货币
negativeNumber: false, // 是否为负数
}
原生方法 Intl.NumberFormat 是对语言敏感的格式化数字类的构造器类
Intl.NumberFormat MDN
今天的主角此时才姗姗来迟, 我们可以使用Intl.NumberFormat方法构造出, 浏览器原生支持的格式化货币的方法, 先看下基础用法:
var number = 123456.789;
// 德语使用逗号作为小数点,使用.作为千位分隔符
console.log(new Intl.NumberFormat('de-DE').format(number));
// → 123.456,789
// 大多数阿拉伯语国家使用阿拉伯语数字
console.log(new Intl.NumberFormat('ar-EG').format(number));
// → ١٢٣٤٥٦٫٧٨٩
// India uses thousands/lakh/crore separators
console.log(new Intl.NumberFormat('en-IN').format(number));
// → 1,23,456.789
pc端的话ie浏览器对Intl.NumberFormat方法的兼容性不好, 它的兼容性如下所示:
image.png
比如我们要转化成中国地区的中国货币格式:
new Intl.NumberFormat(
'zh',
{
style: 'currency',
currency: 'CNY'
}
).format(12.345);
// ¥12.35
上面代码中的 'zh' 参数代表中国地区, currency: 'CNY' 代表使用人民币符号, 我整理了如何查询指定地区的code的网站:
查询国家代码: BCP 47 language tag查询货币代码: ISO 4217 currency codes
每次学习一个新的api总是忍不住试试不按规范填写会发生什么, 比如下面这样:
// 土耳其
new Intl.NumberFormat(
'tr-TR',
{
style: 'currency',
currency: 'TRY'
}
).format(12345.678);
// ₺12.345,68
// 我将地区指定为'土耳其' 货币指定为 '人民币'
new Intl.NumberFormat(
'tr-TR',
{
style: 'currency',
currency: 'CNY'
}
).format(12345.678);
// CN¥12.345,68
上面可以看出货币的小数点与千分位的写法还是'土耳其'的写法规范, 但是货币符号变成了'CN¥', 也就是因为不止一个国家使用¥符号, 所以土耳其当地需要前面加上 CN来区分国家, 所以说本国家展示本国家货币则直接使用原本的货币符号, 而不是中国国内使用CN¥, 而是直接¥。
被本地化的符号
人民币的 '¥' 在土耳其变成了 'CN¥', 我在网上搜索了一下发现两种写法都标识人民币, 所以它是土耳其本地用来区分货币符号的展示方式, 此时我突然想到日元也是 ¥ , 那么土耳其是如何展示这些同样使用 ¥ 符号的?
new Intl.NumberFormat(
'tr-TR',
{
style: 'currency',
currency: "JPY",
}
).format(12345.678);
// ¥12.346 日元默认没有小数
日元是直接展示¥, 所以需要通过写法的不同来区分国家。
我实际业务中遇到了这个场景, 需要把数字转换成多个国家的货币, 所以我们这个插件需要支持如下的使用方式。
初始化各种配置参数, 假设我们的插件导出一个 CurrencyFormat 方法, addFormatType添加格式化配置的时候必须指定一个name, 方便后续调用指定的方法:
下面是我的插件的用法
const currencyFormat = new CurrencyFormat();
currencyFormat.addFormatType("人民币", {
locale:'zh',
currency: "CNY"
// ... 其余配置
});
currencyFormat.addFormatType("新加坡", {
locale:'zh-SG',
currency: "SGD",
// ... 其余配置
});
currencyFormat.addFormatType("日元", {
locale:'ja-JP',
currency: "JPY",
// ... 其余配置
});
这里是使用的方式:
currencyFormat.format('人民币', 12.34)
currencyFormat.format('新加坡', 12.34)
currencyFormat.format('日元', 12.34)
上述的方式就可以实现, 只配置一次, 即可随处调用。
class CurrencyFormat {
formatObj = new Map();
addFormatType(typeName, options) {
const { locale, currency } = options;
const formatFn = new Intl.NumberFormat(locale, {
currency,
style: "currency"
}).format;
this.formatObj.set(typeName, { formatFn });
}
format(typeName, val) {
const formatItem = this.formatObj.get(typeName);
if (formatItem) {
const { formatFn } = formatItem;
return formatFn(val);
}
return "-";
}
}
实例化 Intl.NumberFormat, 然后每次调用对应的实力。
这个可以很直接的通过一个属性即可, useGrouping 为 false的时候则为不展示'千分号'。
new Intl.NumberFormat(locale, {
currency,
style: "currency"
useGrouping: false
});
比如中国默认保留两位小数, 但是用户需要查看小数点后三位的数字, 此时就有必要让用户指定货币格式化后要保留的位数, 参数名称为 maximumFractionDigits:
要注意, 如果 maximumFractionDigits 是个负数的话会报错:
new Intl.NumberFormat(locale, {
currency,
style: "currency"
useGrouping: false,
maximumFractionDigits: 3
});
这里重点是如果用户未指定 maximumFractionDigits, 那么此时就是用户使用的 '国家地区' 的保留小数默认值, 在我的插件里这个默认值后续会用来进行指定计算,那么我要如何知道当前的计算是精确到几位小数?
很遗憾原生没有提供获取某个地区的计算精度的方法, 所以需要人为的计算出来:
计算的时候要注意特殊的打印格式:
// 通过编号系统中的nu扩展键请求, 例如中文十进制数字
console.log(new Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec').format(number));
// → 一二三,四五六.七八九
正则匹配, 假如数字1.234567转换后的'货币字符串'比如 '$1.23' 或者'1.23₫' , 进行从后往前的匹配, 从遇到到第一个数字开始记录, 如果遇到 '.' 或 ',' 则停止匹配返回结构的长度。
但其实有更优雅的正则方式, 当我传入0进行格式化时, 只需要匹配出 "数字1 + 小数点 + 数字2" 的模式中的数字2的长度即可:
let maxFractionDigits = 0;
const currencyTempString = formatFn(0).replace(/\s/g, "");
const regVal = /[0〇]+[\.\,]([0〇]+)/g;
const resArr = regVal.exec(currencyTempString);
if (resArr) {
maxFractionDigits = resArr?.[1]?.length;
}
第一个数字输入数字0, 转换后的'货币字符串'比如 '$0.00' 或者'0.00₫', 第二个数字输入0但同时指定保留小数0位, 然后将两个字符串长度相减后再减一, 限制这个数最小为0。
伪代码: 默认精确位数 = Math.max( '长度0'长度 - '.'长度 , 0)。
这个方式有个大坑, 就是node 13版本之前执行可能会报错, 因为此时 maximumFractionDigits 传入0的时候可能会报错!
Intl.NumberFormat 方法默认是 '四舍五入'的方式来计算金额, 但实际场景很可能需要用户来指定"向下取整"与"向上取整"这样的规则, 我这边定义为通过 calculationType: 'ceil' | 'floor' 来控制, 同时需要maxFractionDigits 这个参数来指定需要保留几位小数:
插件内的使用方法:
currencyFormat.addFormatType("人民币", {
locale:'zh',
currency: "CNY"
calculationType: 'ceil'
});
插件的format代码:
format(typeName, val) {
const formatItem = this.formatObj.get(typeName);
if (formatItem) {
const { formatFn, calculationType, maxFractionDigits } = formatItem;
const multiple = Math.pow(10, maxFractionDigits);
if (calculationType === "ceil" || calculationType === "floor") {
val = Math[calculationType](val * multiple) / multiple;
}
const currencyString = formatFn(val);
return currencyString;
}
return "-";
}
这个代码里我们获取到 calculationType 变量指定的计算类型, 然后将用户传入的数据先乘上 10的maxFractionDigits 次方, 然后进行Math运算, 计算好后再除以 10的 maxFractionDigits 次方。
如果货币为负数, 比如'-12.34'则插件默认返回"-但是有些场景需要展示为-12.34", 如果地区指定为新加坡则展示"-SGD12.35", 明显负号在外层有点看不清了, 那么接下来就处理这个问题。
插件默认返回的格式是"但是实际上可能我们要修改一下样式比如在货币符号与金额中增加空格 12.34", 或者需要隐藏符号只展示"12.34", 还有就是上述的'负数'问题, 所以有必要返回详情给用户:
// 比如格式化 12345.67 为人民币
{
isFront: true, // 货币符号是否在金额前方
currencySymbol: "¥" // 货币符号
formatValue: "12,345.67", // 货币
value: 12345.67, // 原本的值
currencyString: "¥12,345.67", // 格式化后的货币
negativeNumber: false, // 是否为负数
}
新增formatDetail方法, 这里先将数字取绝对值后再进行货币格式化, 代码为:
getFrontCurrencySymbol = (val) => /^[^\d一二三四五六七八九]+/g.exec(val)?.[0] ?? "";
getAfterCurrencySymbol = (val) => /[^\d一二三四五六七八九]+$/g.exec(val)?.[0] ?? "";
formatDetail(typeName, val) {
const value = Math.abs(val);
const currencyString = this.format(typeName, value);
const frontCurrencySymbol = this.getFrontCurrencySymbol(currencyString);
if (frontCurrencySymbol) {
return {
isFront: true,
currencySymbol: frontCurrencySymbol,
formatValue: currencyString.slice(frontCurrencySymbol.length) || "0",
value,
currencyString,
negativeNumber: val < 0
};
}
const afterCurrencySymbol = this.getAfterCurrencySymbol(currencyString);
return {
isFront: false,
currencySymbol: afterCurrencySymbol,
formatValue: currencyString.slice(0, -afterCurrencySymbol.length) || "0",
value,
currencyString,
negativeNumber: val < 0
};
如下的使用方式与返回值:
currencyFormat.addFormatType("中文汉字", {
locale:'zh-Hans-CN-u-nu-hanidec',
currency: "CNY",
});
console.log('中文汉字',currencyFormat.formatDetail('中文汉字', 12345.67));
image.png
所谓缩写就是图里这种模式:
也就是如何展示长数字, 我这里增加了一个formatAbbreviation方法, 此方法可以返回数字被缩写处理后的字符串, 用法如下 :
const currencyFormat = new CurrencyFormat();
currencyFormat.addFormatType("en_gb", {
locale: "en-GB",
currency: "GBP",
});
currencyFormat.formatAbbreviation("en_gb", 123456.789)
// 打印结果是 £123K
配置方案如下, 比如想让越南盾按中文进行缩略:
const currencyFormat = new CurrencyFormat();
currencyFormat.addFormatType("demo_越南_中文", {
locale: 'vi-VN',
currency: "VND",
validAbbreviations: {
"3": '千',
"4": '万',
"8": "亿",
"13": "兆"
},
});
validAbbreviations 属性指定了当数字超过多少位时进行缩略, 并且指明缩略后的符号, 比如上图就是 '1000' 转换为 '1千₫'。
当然我们默认内置了一套基础的转换规则:
validAbbreviationsTypeEN = {
3: "K",
6: "M",
9: "B",
12: "T",
};
那么它的原理我简单阐述下, 其实我是利用了已有的api "formatDetail"来获取到货币详情对象, 此时拿到 真实的数值, 对这个数值与 validAbbreviationsTypeEN进行循环比较 , 看他的位数应该加什么缩略符号。
但是也要注意一点, 就是缩略符号的位置, 因为货币符号有前有后, 所以当货币符号在前则没有特殊处理, 但是货币符号在后, 就需要先展示缩略符再展示货币符, 例如下方展示的:
123456.789 --> Rp12万
123456.789 --> 12万₫
真实业务给我上了一课, 不是所有场景都按一些国际化标准进行的, 比如针对新加坡联盟这边需要展示 "S但是其国际上应该展示"即可, 此时不想按标准展示符号就需要我们可以让开发者自由指定货币符号, 所以我新增了 targetCurrency 属性:
const currencyFormat = new CurrencyFormat();
currencyFormat.addFormatType("en_gb_targetCurrency1", {
locale: "en-GB",
currency: "GBP",
targetCurrency: "xxxx"
});
currencyFormat.format("en_gb_targetCurrency1", 123456.789)
// 最后输出的结果是"xxxx123,456.79
原理也比较直接, 在format方法的最终, 判断是否有targetCurrency属性, 然后将其与当前的货币符号进行替换即可。
这次就是这样, 希望与你一起进步。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/iFxSUJ_LWa-nQEOXpNcQPA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。