UNPKG

safe-currency

Version:

currency tool

248 lines (239 loc) 7.65 kB
import JSBD, { Decimal } from "JSBD"; const defaultSetting = { symbol: "¥", separator: ",", decimal: ".", precision: 2, pattern: "!#", negativePattern: "-!#", showTail0: true, strict: false, fromCents: false, }; export class currency { JSBDVal: Decimal; value: string; private settings: { symbol: string; separator: string; decimal: string; precision: number; pattern: string; negativePattern: string; formatter?: (i: string) => string; showTail0: boolean; strict: boolean; fromCents: boolean; }; constructor( value: number | string | Decimal | bigint | currency, opts?: { symbol?: string; separator?: string; decimal?: string; precision?: number; pattern?: string; negativePattern?: string; formatter?: (i: string) => string; showTail0?: boolean; strict?: boolean; fromCents?: boolean; } ) { this.settings = Object.assign({}, defaultSetting, opts); this.JSBDVal = this.parseInput(value); if (this.settings.fromCents) this.JSBDVal = JSBD.divide(this.JSBDVal, JSBD.BigDecimal(100)); this.settings.fromCents = false; this.value = this.JSBDVal.toFixed(this.settings.precision) .replace(/0+$/, "") .replace(/\.$/, "") .replace(/^$/, "0"); } private static toChineseMoney(num: string): string { let ch = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"], ch_u = ["个", "拾", "佰", "仟", "万", "亿"]; let isNegative = num.charAt(0) === "-"; num = isNegative ? num.substring(1) : num; let [int, decimal] = num.split("."); let int_str = decimal === "00" ? "整" : "", decimal_str = ""; if (decimal === "00") decimal_str = ""; else if (decimal.charAt(0) === "0") decimal_str = "零" + ch[+decimal.charAt(1)] + "分"; else if (decimal.charAt(1) === "0") decimal_str = ch[+decimal.charAt(0)] + "角"; else decimal_str = ch[+decimal.charAt(0)] + "角" + ch[+decimal.charAt(1)] + "分"; if (int != "0" || decimal_str === "") { const int2ChineseNumber = (intStr: string) => { intStr = intStr.replace(/^0+/, "").replace(/^$/, "0"); let int = "", strLen = intStr.length; if (strLen == 1) return ch[+intStr]; if (strLen <= 4) { for (let i = 0, n = strLen - 1; n >= 0; n--, i++) { let _num = +intStr.charAt(i); int += ch[_num]; int += _num && n ? ch_u[n] : ""; } } else { //大数递归 let d = (intStr.length / 4) >> 0, y = intStr.length % 4; while (y == 0 || !ch_u[3 + d]) { y += 4; d--; } let left = intStr.substring(0, y), right = intStr.substring(y); int = int2ChineseNumber(left) + ch_u[3 + d] + (right.charAt(0) == "0" ? "零" : "") + int2ChineseNumber(right); } return int.replace(/零+$/, "").replace(/零{2}/g, "零"); }; int_str = int2ChineseNumber(int) + "元" + int_str; } return (isNegative ? "负" : "") + int_str + decimal_str; } private parseInput( val: number | string | currency | Decimal | BigInt ): Decimal { if (!this.testValid(val)) throw "number format error"; if (typeof val === "undefined" || val === null) return JSBD.BigDecimal(0); if (val instanceof currency) return JSBD.BigDecimal(val.JSBDVal); if (val instanceof Decimal) return JSBD.BigDecimal(val); if (typeof val === "string") { let regex = new RegExp("[^-\\d" + this.settings.decimal + "]", "g"), decimalString = new RegExp("\\" + this.settings.decimal, "g"); val = val .replace(/\((.*)\)/, "-$1") .replace(regex, "") .replace(decimalString, ".") .replace(/^0+/, "") .replace(/^$/, "0"); //('123ab.f3')=>'123.3' return JSBD.BigDecimal(val); } return JSBD.BigDecimal(<number | bigint>val); } private testValid( val: number | string | currency | Decimal | BigInt ): boolean { if (this.settings.strict === false) return true; if (val === null || typeof val === "undefined") return false; if (typeof val === "string") return /^[-]?[\.\d]+$/.test(val); return true; } add(val: number | string | currency | Decimal | BigInt): currency { return new currency( JSBD.add(this.JSBDVal, this.parseInput(val)), this.settings ); } subtract(val: number | string | currency | Decimal | BigInt): currency { return new currency( JSBD.subtract(this.JSBDVal, this.parseInput(val)), this.settings ); } multiply(val: number | string | currency | Decimal | BigInt): currency { return new currency( JSBD.multiply(this.JSBDVal, this.parseInput(val)), this.settings ); } divide(val: number | string | currency | Decimal | BigInt): currency { return new currency( JSBD.divide(this.JSBDVal, this.parseInput(val)), this.settings ); } compareTo(b: currency): number { if (JSBD.greaterThan(this.JSBDVal, b.JSBDVal)) return 1; if (JSBD.equal(this.JSBDVal, b.JSBDVal)) return 0; return -1; } distribute(count: number): Array<currency> { count = Math.floor(count); const arr: currency[] = []; const divideRes = JSBD.divide(this.JSBDVal, JSBD.BigDecimal(count), { maximumFractionDigits: this.settings.precision, roundingMode: "down", }); const reminder = JSBD.subtract( this.JSBDVal, JSBD.multiply(divideRes, JSBD.BigDecimal(count)) ); let minUnit = this.settings.precision === 0 ? JSBD.BigDecimal(1) : JSBD.pow(JSBD.BigDecimal(0.1), this.settings.precision); if (JSBD.greaterThan(JSBD.BigDecimal(0), reminder)) minUnit = JSBD.multiply(minUnit, JSBD.BigDecimal(-1)); let num = +JSBD.divide(reminder, minUnit).toFixed(); for (let i = 0; i < count; i++, num--) { if (num > 0) arr[i] = new currency(JSBD.add(divideRes, minUnit), this.settings); else arr[i] = new currency(divideRes, this.settings); } return arr; } format(opts?: { symbol?: string; separator?: string; decimal?: string; precision?: string; pattern?: string; negativePattern?: string; showTail0?: true; formatter?: (arg0: string) => string; }): string { let { pattern, negativePattern, symbol, separator, decimal, formatter } = Object.assign({}, this.settings, opts); if (formatter) return formatter(this.value); let split = this.JSBDVal.toFixed(this.settings.precision) .replace(/^-/, "") .split("."), yuan = split[0], cents = split[1]; if (!this.settings.showTail0) { cents = cents.replace(/0+$/, ""); if (cents.length === 0) decimal = ""; } return ( JSBD.greaterThanOrEqual(this.JSBDVal, JSBD.BigDecimal(0)) ? pattern : negativePattern ) .replace("!", symbol) .replace( "#", yuan.replace(/(\d)(?=(\d{3})+\b)/g, "$1" + separator) + (cents ? decimal + cents : "") ); } toString(): string { return this.value; } toNumber(): number { return parseFloat(this.value); } toJSON(): string { return this.value; } yuan(): string { return this.value; } cents(): string { return JSBD.multiply(this.JSBDVal, JSBD.BigDecimal(100)).toFixed(); } chineseNumber(): string { return currency.toChineseMoney(this.JSBDVal.toFixed(2)); } } export default currency;