-
Notifications
You must be signed in to change notification settings - Fork 0
/
Money.ts
141 lines (135 loc) · 4.05 KB
/
Money.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import Rational from "./Rational";
import Dense from "./Dense";
import Discrete, { Scale } from "./Discrete";
/**
* Represents the ratio to convert a source currency to a destination currency
*/
export type ExchangeRate<Src extends string, Dst extends string> = {
/**
* the name of the source currency
*/
readonly src: Src;
/**
* the name of the target currency
*/
readonly dst: Dst;
/**
* the ratio by which to multiply the source value to get the target value
*/
readonly ratio: Rational;
};
/**
* create a new exchange rate
*
* @param src the source currency name
* @param dst the target currency name
* @param ratio the ratio by which to multiply the source value to get the target value
*/
export const exchangeRate = <Src extends string, Dst extends string>(
src: Src,
dst: Dst,
ratio: Rational
): ExchangeRate<Src, Dst> => ({ src, dst, ratio });
/**
* converts a dense value from a source currency to a target currency based on the given exchange rate
*
* @param rate the exchange rate
* @param src a dense value in the source currency
*/
export const exchange = <
Src extends string,
Dst extends string,
DenseSrc extends Dense<Src>
>(
rate: ExchangeRate<Src, Dst>,
src: DenseSrc
): Dense<Dst> => Dense.of(src.value.mul(rate.ratio), rate.dst);
/**
* floors the given dense value to the closest discrete value in the given scale
*
* @param value the dense value
* @param scale the discrete scale
*/
export const floor = <
Currency extends string,
Unit extends string,
DenseVal extends Dense<Currency>
>(
value: DenseVal,
scale: Scale<Currency, Unit>
): [Discrete<Currency, Unit>, Dense<Currency>] => {
const [num, denom] = value.value.mul(scale.ratio).normalize().asTuple();
const neg = num < 0;
const absNum = neg ? -num : num;
const x = absNum / denom;
const rem = absNum - x * denom;
const absVal = neg && rem > 0 ? x + BigInt(1) : x;
const mag = Math.pow(10, rem.toString().length);
const remVal = Rational.of(rem, mag).div(scale.ratio);
return [
Discrete.of(neg ? -absVal : absVal, scale),
Dense.of(neg ? scale.ratio.inverse().sub(remVal) : remVal, scale.currency),
];
};
/**
* ceils the given dense value to the closest discrete value in the given scale
*
* @param value the dense value
* @param scale the discrete scale
*/
export const ceil = <
Currency extends string,
Unit extends string,
DenseVal extends Dense<Currency>
>(
value: DenseVal,
scale: Scale<Currency, Unit>
): [Discrete<Currency, Unit>, Dense<Currency>] => {
const [num, denom] = value.value.mul(scale.ratio).normalize().asTuple();
const neg = num < 0;
const absNum = neg ? -num : num;
const x = absNum / denom;
const rem = absNum - x * denom;
const absVal = !neg && rem > 0 ? x + BigInt(1) : x;
const mag = Math.pow(10, rem.toString().length);
const remVal = Rational.of(rem, mag).div(scale.ratio).mul(Rational.nat(-1));
return [
Discrete.of(neg ? -absVal : absVal, scale),
Dense.of(
!neg && rem > 0 ? scale.ratio.inverse().negate().sub(remVal) : remVal,
scale.currency
),
];
};
/**
* rounds the given dense value to the closest discrete value with the given scale
*
* @param value the dense value
* @param scale the discrete scale
*/
export const round = <
Currency extends string,
Unit extends string,
DenseVal extends Dense<Currency>
>(
value: DenseVal,
scale: Scale<Currency, Unit>
): [Discrete<Currency, Unit>, Dense<Currency>] => {
const [num, denom] = value.value.mul(scale.ratio).normalize().asTuple();
const neg = num < 0;
const absNum = neg ? -num : num;
const x = absNum / denom;
const rem = absNum - x * denom;
const remStr = rem.toString();
const sig = parseInt(remStr[0], 10);
const absVal = sig >= 5 ? x + BigInt(1) : x;
const mag = Math.pow(10, remStr.length);
const remVal = Rational.of(rem, mag).div(scale.ratio);
return [
Discrete.of(neg ? -absVal : absVal, scale),
Dense.of(
sig >= 5 ? scale.ratio.inverse().sub(remVal) : remVal,
scale.currency
).mul(Rational.nat((sig < 5 && neg) || (sig >= 5 && !neg) ? -1 : 1)),
];
};