safe-currency
Version:
currency tool
248 lines (239 loc) • 7.65 kB
text/typescript
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;