钱
快速实例
Money.of(2090.5, "EUR").toCurrency("NOK", 8.61).toString(); // 17999.21
Money.of(0.1, "NOK").add(Money.of(0.2, "NOK")).toString(); // 0.30
Money.fromPriceAndQuantity(0.0005, 30, "NOK").toString(); // 0.02
Money.fromPrice(0.001, "NOK").multiply(60).resetDecimals().toString(); // 0.06
Money.of(10, "NOK").distributeBy([1, 1, 1]); // list of moneys: [3.34, 3.33, 3.33]
Money.of(5.5, "NOK").toLocaleString("no-NB"); // 5,50
Money.fromLocaleString("5,50", "NOK", "no-NB").toString(); // 5.50
Money.of(12.5, "NOK")
.getVat(25, true) // Amount includes VAT
.toString(); // 2.50
Money.of(10, "NOK")
.getVat(25, false) // Amount does not include VAT
.toString(); // 2.50
Money.of("1234567891234567.25", "NOK").toNumber(); // Throws because the number cannot be represented as a double
见此页下方的全阿皮.
特征
- 基于大.js,任意的基数为十进制. 没有奇怪的结果 由于基础2双.
- 基本数学和比较
- 确保特定货币的准确性
- 在计算中防止混合货币
- 任意精确地处理价格
- 从本地打印和剖析
- 从分数/ 分数/ 分数/ 整分数打印和剖析
- 圆相模式( 从 big.js.). 例如,四舍五入(银行家四舍五入)
- 转换为 Javascript 数字时遇到精度损失时投掷
- 钱物有标签和标签断言. 例如, 包含 Vat= 的标记以知道该数额包含 vat .
- 一切都是不可改变的
- 各种倾斜
- 计算增值税
- 按零件或重量分配资金(直视师与资金无关)
关于钱在javascript / 为什么存在这个库
Javascript中的数字不适合用钱计算
Javascript使用双浮点数.
在处理资金方面,有好几个问题:
- 基数2不能准确代表所有基数10(如:0.1+0.2). 这使得一些计算不准确,无论我们选择的精确度如何. 应当指出,这并不意味着我们不能准确地在两个基地之间转换, 虽然应该提及到Fixed( ) 不是精确的转换
- 双精度不是无限精度. 当我们需要大量数字时,或者需要许多分数的数字(如价格)时,这一点很重要
- 在javascript中默认四舍五入对负金额是错误的
比较容易以实例显示:
// Without proper rounding, you'll accumulate small errors:
0.1 + 0.2; // => 0.30000000000000004
// With proper rounding, you'll simply get wrong results:
2090.5 * 8.61; // => 17999.204999999998
(2090.5 * 8.61).toFixed(2); // => 17999.20 // should have been 17999.21
// Even if the console.log manages to convert to base 10 for you,
// the number might not be what you think it is inside the double,
// and toFixed doesn't actually convert to base 10 properly.
// (see also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed)
(8.165).toFixed(2); // => 8.16 // should have been 8.17
// Math.round rounds towards 0, which doesn't make sense with money:
Math.round(-1.5) - // => -1 // Should have been -2 for money
// toFixed does it the correct way though:
(1.5).toFixed(0); // => -2 // Say what now
// 16 decimal digits are too much for javascript doubles
// Note how it doesn't really matter whether we're using whole numbers or not
Number(9999999999999999).toString(); // => 10000000000000000 // Too big for most money amounts, so probably fine
Number(999999999999999.9).toString(); // => 999999999999999.9
Number(99999999999999.99).toString(); // => 99999999999999.98
Number(9999999999999.999).toString(); // => 9999999999999.998
Number(999999999999.9999).toString(); // => 999999999999.9999
Number(99999999999.99999).toString(); // => 99999999999.99998
Number(9999999999.999999).toString(); // => 9999999999.999998
Number(999999999.9999999).toString(); // => 999999999.9999999
Number(99999999.99999999).toString(); // => 99999999.99999999
Number(9999999.999999999).toString(); // => 9999999.999999998 // If this was a price we would be in trouble
这个图书馆使用优异的大. js 库进行任意小数( 第 10 个基数) 算术. 这自动处理上述所有问题.
整分钱都不够
人们常说,一个人应该使用货币的"小"单位的整数,例如一整分. 这对运输和储存来说(也许见下文)是完全罚款的,但精确计算则不是罚款的.
这就是为什么:
- 你还得分 师会引入小数
- 您仍然必须转换货币, 利率有小数.
- 你仍然需要处理价格, 这些可以有任意的小数. 您需要大写来对付那些 因为Javascript只有53个位数(15个小数)可用于整数.
- 您可能还需要在某个时间点转换到和从小数位转换,而.toFixed()并不准确
- 您必须跟踪货币和精确度/ 比例 。 任何错误,你就会失去数量级. 这可能比你想象的更头痛 如果你需要以任意的精确度覆盖价格. 在这种情况下,问题也在于储存和运输.
在这个库中,我们选择使用大.js 来避免所有潜在的陷阱 与整个数字计算.
运送/储存金额作为日记号
双胞胎有53个比特 这是准确的部分. 53个二进制数字相当于15.95个十进制数字,或15个是安全的.
因此,我们有15位数字可以合作。 我们能装多少?
世界GDP约为8*10^13美元,需要14位数字。 一分钱就是16个数字. 因此,在美元中,我们只是能够 将全世界的GDP 以美分计,而不失去精确度.
注意,双胞胎的代词不会改变我们得到的精确数字数, 所以,它并不重要,如果我们把它作为一个整体数字或一个分数存储.
无论如何, Javascript数字很可能 足以用于运输和储存 大多数使用案例, 即使是分数。 然而,有一些潜在的陷阱:
- JSON标准没有定义数字的精度,由解析器处理. 不过,至少假定双重精确性似乎相当合理.
- 大多数货币在小数点后有0到4个十进制,但加密货币可以拥有更多的. 这可能导致精确要求上升.
- 如果你处理价格,所有的赌注是关闭的。 价格可以任意精确.
在选择如何储存和运输之前,你必须知道自己的数字会有多大和精确.
这个图书馆给你一些选择:
- 作为分数 / 全数
- 作为正常数字
- 作为字符串
正如我们所看到的那样,前两个有潜在的问题(尽管不太可能), 但为了减轻风险 如果你遇到这些麻烦 我们一定会破例.
如果你想完全安全,就使用绳子.
关于金钱的法律业务
钱是用一种货币,它通常有一个主(如美元)和小单位(如分币).
- 金额不能小于小单位(如半分).
- 价格可以有任意的精确度,但是在四舍五入之前价格不是钱.
- 精确规则的一个后果是,金钱不能被平分成块(例如,10美元分成3个块将是[3.34, 3.33, 3.33])
- 货币数学和比较无法进行。 货币转换为共同货币必须先行进行.
这个库确保一个人以正确的货币运作,并正确精确地使用给定的货币. 对于中间计算和价格,可以调整精度.
业绩
大.js是相当快和轻量级, 但远未接近 本地数字的性能.
以下代码需要0.06ms左右,而仅添加本身大约是0.02ms: 1
Money.of(Math.random(), "NOK").add(Money.of(Math.random(), "NOK")).toNumber();
这样你就可以在60英尺范围内做大约260个, 或大约800个纯添加. 如果我们在谈论更大的工作, 你可以在5分钟内完成以上3亿.
API 密码
export type AdditionalOptions = {
decimals?: number; // Override precision
roundingMode?: RoundingMode; // See big.js rounding modes
tags?: Partial<Tags>; // Tag your money to keep track of what it represents
};
export declare class Money {
constructor(data: MoneyInputData);
/**
* Create a money object.
*
* Amount can be any of number, string, or Big
* currency is the 3-character currency code (ISO 4217)
* currency can also be set to UNKNOWN, which gives a precision of 2 decimals.
*
* With options you can specify
* - decimals
* - roundingMode
* - tags
*/
static of(
amount: NumberInput,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Instantiate from a string formatted in a certain locale.
*
* Examples:
* no-NB: 11 111,11
* en-GB: 11,111.11
* de-DE: 11.111,11
*
* Before parsing, non-numeric characters are removed, and the decimal sign is normalized.
*
* Locales with unicode numbers are NOT SUPPORTED
* Example of formats NOT SUPPORTED:
* ar: ١١٬١١١٫١١
*/
static fromLocaleString(
str: string,
currency: string,
locale?: string,
options?: AdditionalOptions,
): Money;
/**
* Instantiate from a whole number of minor units of the currency (e.g. whole number of cents)
*
* Example:
* Money.fromFractionlessAmount(1000, 'NOK') => 10.00 NOK
* Money.fromFractionlessAmount(1000, 'NOK', { decimals: 2 }) => 10.00 NOK
* Money.fromFractionlessAmount(1000, 'NOK', { decimals: 3 }) => 1.000 NOK
*/
static fromFractionlessAmount(
amount: number,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* A price has arbitrary precision.
* This method creates a Money instance with 6 decimals of precision by default.
* Remember to call .resetDecimals() when you want to go back to a proper Money value.
*/
static fromPrice(
price: NumberInput,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Calculate total money according to a price and quantity.
* Default precision is 6 decimals.
*/
static fromPriceAndQuantity(
price: NumberInput,
quantity: Factor,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Sum an array of moneys.
*
* If the array is empty, a currency must be specified so that 0 can be returned in that currency.
*
* The precision, rounding mode, etc, is based on the first item in the array.
* If the array is empty, the options object will be used instead.
*/
static sum(
moneys: Money[],
currency?: string,
options?: AdditionalOptions,
): Money;
static max(moneys: Money[]): Money;
static min(moneys: Money[]): Money;
/**
* Compare two money objects.
*
* 1 if money1 is greater than money2
* 0 if equal
* -1 if money1 is less than money2
*
* This can be plugged directly into array.sort(),
* and it will cause the array to be sorted in ascending order.
*/
static compare(money1: Money, money2: Money): number;
merge: (data: Partial<MoneyInputData>) => Money;
// Tags
/**
* Tags allow you to communicate more about what the money represents.
* You can later assert on a tag to make sure you're using the right amount for the right purpose.
*/
getTags: () => Tags;
getTag: <Name extends keyof Tags, Value>(
tagName: Name,
defaultValue?: Value | undefined,
) => Value | undefined;
setTag: <Name extends keyof Tags>(tagName: Name, value: any) => Money;
// Assertions
assertTag: <Name extends keyof Tags>(
tagName: Name,
value: any,
cmp?: (actual: any, value: any) => boolean,
) => Money;
assertSameCurrency: (money: Money) => Money;
// Utils
amount: () => Big;
currency: () => string;
// Converters
/**
* Converts the money amount into a whole number given in the minor unit of the currency.
* Honors the current precision in use.
*/
toFractionlessAmount: () => number;
/**
* Converts to a regular javascript number.
* Throws an error if it's not possible to do keep full precision.
*/
toNumber: () => number;
toString: () => string;
toLocaleString: (locale?: string | undefined) => string;
toJSON: () => number;
/**
* Gets the current precision in use.
*/
getDecimals: () => number;
/**
* Override the default precision of the currency.
* Useful if you're working with a price.
*/
setDecimals: (decimals: number) => Money;
/**
* Reset precision back to that of the currency.
* Useful for converting from a price to the final monetary amount.
*/
resetDecimals: () => Money;
/**
* Converts to a different currency using a currency rate.
* Sometimes (rarely) the rate is given multiplied by a certain unit amount which has to be divided away.
*/
toCurrency: (
currency: string,
currencyRate?: Factor,
currencyUnit?: Factor,
) => Money;
//Math
round: (decimals: number, roundingMode?: RoundingMode | undefined) => Money;
multiply: (factor: Factor) => Money;
/**
* Note that dividing a monetary amount cannot be exact in all cases.
* E.g. 10 NOK / 3 = 3.33 NOK
* Use `distribute` or `distributeBy` if you need an exact distribution.
*
* The division is performed with a precision of 20 decimals before
* rounding back to the monetary amount. (See https://mikemcl.github.io/big.js/#dp)
*/
divide: (divisor: Factor) => Money;
add: (money: Money) => Money;
subtract: (money: Money) => Money;
abs: () => Money;
// Comparisons
equals: (money: Money) => boolean;
greaterThan: (money: Money) => boolean;
greaterThanOrEqual: (money: Money) => boolean;
lessThan: (money: Money) => boolean;
lessThanOrEqual: (money: Money) => boolean;
isZero: () => boolean;
/**
* Positive and not 0
*/
isPositive: () => boolean;
/**
* Negative and not 0
*/
isNegative: () => boolean;
// Distribute
/**
* Divides money into n parts.
*
* Example:
* Money.of(10, 'NOK').distribute(3) => [3.34, 3.33, 3.33]
*
* Distributes any rest amount equally across the parts
*/
distribute: (nParts: number) => Money[];
/**
* Divides money into parts, each defined by a weight.
*
* Each weight must be >= 0
* Total of weights must be > 0
*
* Example:
* Money.of(10, 'NOK').distributeBy([1, 1, 1]) => [3.34, 3.33, 3.33]
*
* Distributes any rest amount equally across the parts
*/
distributeBy: (inputWeights: Factor[]) => Money[];
// VAT
addVat: (vatPercentage: Factor) => Money;
removeVat: (vatPercentage: Factor) => Money;
getVat: (vatPercentage: Factor, includesVat?: boolean | undefined) => Money;
}