金额数字转换

3,539 阅读4分钟

前段时间,项目遇到一个需求,将金额的阿拉伯数字转成大写,而且是西班牙语,网上搜索未果,于是自己撸了一个 cardinal-number。目前已支持三种语言,汉语、西班牙语、英语。

先上示例

import convert, { locale } from 'cardinal-number';

console.log(convert(90009)) // 玖万零玖元整
console.log(convert(90009.23)) // 玖万零玖元贰角叁分
locale('es')
console.log(convert(199)) // ciento noventa y nueve peso 00/100 MIN 
locale('en')
console.log(convert(73.02)) // seventy-three and cents two only

思路历程

每种语言特性不太一样,我们汉语是万分制12345678 => 1234,5678, 英语和西班牙语是千分制12345678 => 12,345,678,即按4位和3位为一组。

因此,只需实现一个基础函数baseFunction处理4位和3位数字,然后将原数字按4和3划分为组,循环调用baseFunction即可。

后来又发现,转成汉语如果按位处理仿佛更简便一点。 按照我们人为思维方式,从左向右,分别为仟亿、佰亿、拾亿、亿、仟万、佰万、拾万......

实现过程

1. 按位处理

将数字按位拆分成数组,如: 789123456789 => [7,8,9,1,2,3,4,5,6,7,8,9]

定义好基础数字object baseMap和位数单位object indexMap

const baseMap = {
	0: '零',
	1: '壹',
	...
    8: '捌',
    9: '玖'
}

const indexMap = {
	1: '拾', // 10^1 => 10
  	2: '佰', // 10^2 => 100
    ...
    8: '亿', // 10^8 => 100000000
    9: '拾', // 10^9 => 1000000000
    10: '佰', // 10^10 => 10000000000 
  	11: '仟', // 10^11 => 100000000000
}

特殊说明一下,我们描述789100000000柒仟捌佰玖拾壹亿 而不是柒仟亿捌捌亿玖拾亿壹亿,因此只需数字第8位的亿就可以了, 同理位也一样。所以仟亿、佰亿、拾亿、仟万、佰万、拾万特殊定义成仟、佰、拾。

对于0的特殊处理。

  • 如果当前位为0,只记录,不会记录当前位的单位。
  • 0的相邻元素也为0时,只需保留一个。如: 3001 => 叁仟零壹, 而不是叁仟零零壹
  • 如果万位,要在对应的拾万、佰万、仟万位补上。如: 300001 => 叁拾万零壹
  • 亿位为0时同上。

2.分组处理(以英语为例)

将数字分组,如: 23456789 => [23, 456, 789]

只需实现一个baseFunction实现3位数的转换,然后循环调用再拼上对应的千(thousand)百万(million)十亿(billion)单位即可。

英语、西班牙语还有一点特殊,英语20以内的数不同,西班牙语30以内的数不同, 而且西班牙语在一些特殊情况下,还有复数和词的转换。下面专门列出部分特性。

2.1 en(英语)

  • 20以内不一样。
1 one    11 eleven
2 two    12 twelve
3 three  13 thirteen
4 four   14 fourteen
5 five   15 fifteen
6 six    16 sixteen
7 seven  17 seventeen 
8 eight  18 eighteen
9 nine   19 nineteen
10 ten   20 twenty
  • 21~99, 可以十位个位拼接, 如: 23 => 20 + 3 => twenty-three
  • 百位和十位之间用and拼接, 如: 123 => one hundred and twenty-three

2.2 es(西班牙语)

特性

  • 30以内不一样。
1 uno 11 once 21 veintiuno

2 dos 12 doce 22 veintidós

3 tres 13 trece 23 veintitrés

4 cuatro 14 catorce 24 veinticuatro

5 cinco 15 quince 25 veinticinco

6 seis 16 dieciséis 26 veintiséis

7 siete 17 diecisiete 27 veintisiete

8 ocho 18 dieciocho 28 veintiocho

9 nueve 19 diecinueve 29 veintinueve

10 diez 20 veinte 30 treinta
  • 31~99, 可以拆成十位和个位相加,32 => 30 + 2 => treinta y dos
  • 100用cien, 超过100用ciento,如100 => cien102 => ciento dos
  • 1百万用millón, 百万位数大于1时用millones。

补充

twenty-threetreinta y dos像这样的数字描述,一般称为基数词。但是这种描述大多情况也是用来描述金额的数值, 因此汉语转成壹、贰、叁...描述金额的数字不是一、二、三...

同时,可能多个国家用同一语种,但是金钱单位描述可能不同, 因此也支持只做基数转换或者自定义转换金钱单位的函数。如下


convert(73.02) => seventy-three and cents two only
convert(73.02, false) => seventy-three // 如果仅转换基数,会忽略小数位

const formatPoints = (num: number): string[] => {
  return ['aaaakekekekekeke'];
};

convert(73.02, formatPoints) => seventy-three aaaakekekekekeke

如有需要欢迎使用, 目前也做了单元测试

File% Stmts% Branch% Funcs% LinesUncovered Line #s
zh.ts10096.8810010045
en.ts10096.8810010061
es.ts10093.7510010061,77

最后

欢迎各位大佬指正。项目地址 cardinal-number, 欢迎star、issue、pr。