UNPKG

13.3 kBJavaScriptView Raw
1"use strict";
2import { arrayify, hexZeroPad, isBytes } from "@ethersproject/bytes";
3import { Logger } from "@ethersproject/logger";
4import { version } from "./_version";
5const logger = new Logger(version);
6import { BigNumber, isBigNumberish } from "./bignumber";
7const _constructorGuard = {};
8const Zero = BigNumber.from(0);
9const NegativeOne = BigNumber.from(-1);
10function throwFault(message, fault, operation, value) {
11 const params = { fault: fault, operation: operation };
12 if (value !== undefined) {
13 params.value = value;
14 }
15 return logger.throwError(message, Logger.errors.NUMERIC_FAULT, params);
16}
17// Constant to pull zeros from for multipliers
18let zeros = "0";
19while (zeros.length < 256) {
20 zeros += zeros;
21}
22// Returns a string "1" followed by decimal "0"s
23function getMultiplier(decimals) {
24 if (typeof (decimals) !== "number") {
25 try {
26 decimals = BigNumber.from(decimals).toNumber();
27 }
28 catch (e) { }
29 }
30 if (typeof (decimals) === "number" && decimals >= 0 && decimals <= 256 && !(decimals % 1)) {
31 return ("1" + zeros.substring(0, decimals));
32 }
33 return logger.throwArgumentError("invalid decimal size", "decimals", decimals);
34}
35export function formatFixed(value, decimals) {
36 if (decimals == null) {
37 decimals = 0;
38 }
39 const multiplier = getMultiplier(decimals);
40 // Make sure wei is a big number (convert as necessary)
41 value = BigNumber.from(value);
42 const negative = value.lt(Zero);
43 if (negative) {
44 value = value.mul(NegativeOne);
45 }
46 let fraction = value.mod(multiplier).toString();
47 while (fraction.length < multiplier.length - 1) {
48 fraction = "0" + fraction;
49 }
50 // Strip training 0
51 fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
52 const whole = value.div(multiplier).toString();
53 if (multiplier.length === 1) {
54 value = whole;
55 }
56 else {
57 value = whole + "." + fraction;
58 }
59 if (negative) {
60 value = "-" + value;
61 }
62 return value;
63}
64export function parseFixed(value, decimals) {
65 if (decimals == null) {
66 decimals = 0;
67 }
68 const multiplier = getMultiplier(decimals);
69 if (typeof (value) !== "string" || !value.match(/^-?[0-9.]+$/)) {
70 logger.throwArgumentError("invalid decimal value", "value", value);
71 }
72 // Is it negative?
73 const negative = (value.substring(0, 1) === "-");
74 if (negative) {
75 value = value.substring(1);
76 }
77 if (value === ".") {
78 logger.throwArgumentError("missing value", "value", value);
79 }
80 // Split it into a whole and fractional part
81 const comps = value.split(".");
82 if (comps.length > 2) {
83 logger.throwArgumentError("too many decimal points", "value", value);
84 }
85 let whole = comps[0], fraction = comps[1];
86 if (!whole) {
87 whole = "0";
88 }
89 if (!fraction) {
90 fraction = "0";
91 }
92 // Trim trailing zeros
93 while (fraction[fraction.length - 1] === "0") {
94 fraction = fraction.substring(0, fraction.length - 1);
95 }
96 // Check the fraction doesn't exceed our decimals size
97 if (fraction.length > multiplier.length - 1) {
98 throwFault("fractional component exceeds decimals", "underflow", "parseFixed");
99 }
100 // If decimals is 0, we have an empty string for fraction
101 if (fraction === "") {
102 fraction = "0";
103 }
104 // Fully pad the string with zeros to get to wei
105 while (fraction.length < multiplier.length - 1) {
106 fraction += "0";
107 }
108 const wholeValue = BigNumber.from(whole);
109 const fractionValue = BigNumber.from(fraction);
110 let wei = (wholeValue.mul(multiplier)).add(fractionValue);
111 if (negative) {
112 wei = wei.mul(NegativeOne);
113 }
114 return wei;
115}
116export class FixedFormat {
117 constructor(constructorGuard, signed, width, decimals) {
118 if (constructorGuard !== _constructorGuard) {
119 logger.throwError("cannot use FixedFormat constructor; use FixedFormat.from", Logger.errors.UNSUPPORTED_OPERATION, {
120 operation: "new FixedFormat"
121 });
122 }
123 this.signed = signed;
124 this.width = width;
125 this.decimals = decimals;
126 this.name = (signed ? "" : "u") + "fixed" + String(width) + "x" + String(decimals);
127 this._multiplier = getMultiplier(decimals);
128 Object.freeze(this);
129 }
130 static from(value) {
131 if (value instanceof FixedFormat) {
132 return value;
133 }
134 if (typeof (value) === "number") {
135 value = `fixed128x${value}`;
136 }
137 let signed = true;
138 let width = 128;
139 let decimals = 18;
140 if (typeof (value) === "string") {
141 if (value === "fixed") {
142 // defaults...
143 }
144 else if (value === "ufixed") {
145 signed = false;
146 }
147 else {
148 const match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/);
149 if (!match) {
150 logger.throwArgumentError("invalid fixed format", "format", value);
151 }
152 signed = (match[1] !== "u");
153 width = parseInt(match[2]);
154 decimals = parseInt(match[3]);
155 }
156 }
157 else if (value) {
158 const check = (key, type, defaultValue) => {
159 if (value[key] == null) {
160 return defaultValue;
161 }
162 if (typeof (value[key]) !== type) {
163 logger.throwArgumentError("invalid fixed format (" + key + " not " + type + ")", "format." + key, value[key]);
164 }
165 return value[key];
166 };
167 signed = check("signed", "boolean", signed);
168 width = check("width", "number", width);
169 decimals = check("decimals", "number", decimals);
170 }
171 if (width % 8) {
172 logger.throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width);
173 }
174 if (decimals > 80) {
175 logger.throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals);
176 }
177 return new FixedFormat(_constructorGuard, signed, width, decimals);
178 }
179}
180export class FixedNumber {
181 constructor(constructorGuard, hex, value, format) {
182 logger.checkNew(new.target, FixedNumber);
183 if (constructorGuard !== _constructorGuard) {
184 logger.throwError("cannot use FixedNumber constructor; use FixedNumber.from", Logger.errors.UNSUPPORTED_OPERATION, {
185 operation: "new FixedFormat"
186 });
187 }
188 this.format = format;
189 this._hex = hex;
190 this._value = value;
191 this._isFixedNumber = true;
192 Object.freeze(this);
193 }
194 _checkFormat(other) {
195 if (this.format.name !== other.format.name) {
196 logger.throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other);
197 }
198 }
199 addUnsafe(other) {
200 this._checkFormat(other);
201 const a = parseFixed(this._value, this.format.decimals);
202 const b = parseFixed(other._value, other.format.decimals);
203 return FixedNumber.fromValue(a.add(b), this.format.decimals, this.format);
204 }
205 subUnsafe(other) {
206 this._checkFormat(other);
207 const a = parseFixed(this._value, this.format.decimals);
208 const b = parseFixed(other._value, other.format.decimals);
209 return FixedNumber.fromValue(a.sub(b), this.format.decimals, this.format);
210 }
211 mulUnsafe(other) {
212 this._checkFormat(other);
213 const a = parseFixed(this._value, this.format.decimals);
214 const b = parseFixed(other._value, other.format.decimals);
215 return FixedNumber.fromValue(a.mul(b).div(this.format._multiplier), this.format.decimals, this.format);
216 }
217 divUnsafe(other) {
218 this._checkFormat(other);
219 const a = parseFixed(this._value, this.format.decimals);
220 const b = parseFixed(other._value, other.format.decimals);
221 return FixedNumber.fromValue(a.mul(this.format._multiplier).div(b), this.format.decimals, this.format);
222 }
223 floor() {
224 const comps = this.toString().split(".");
225 if (comps.length === 1) {
226 comps.push("0");
227 }
228 let result = FixedNumber.from(comps[0], this.format);
229 const hasFraction = !comps[1].match(/^(0*)$/);
230 if (this.isNegative() && hasFraction) {
231 result = result.subUnsafe(ONE.toFormat(result.format));
232 }
233 return result;
234 }
235 ceiling() {
236 const comps = this.toString().split(".");
237 if (comps.length === 1) {
238 comps.push("0");
239 }
240 let result = FixedNumber.from(comps[0], this.format);
241 const hasFraction = !comps[1].match(/^(0*)$/);
242 if (!this.isNegative() && hasFraction) {
243 result = result.addUnsafe(ONE.toFormat(result.format));
244 }
245 return result;
246 }
247 // @TODO: Support other rounding algorithms
248 round(decimals) {
249 if (decimals == null) {
250 decimals = 0;
251 }
252 // If we are already in range, we're done
253 const comps = this.toString().split(".");
254 if (comps.length === 1) {
255 comps.push("0");
256 }
257 if (decimals < 0 || decimals > 80 || (decimals % 1)) {
258 logger.throwArgumentError("invalid decimal count", "decimals", decimals);
259 }
260 if (comps[1].length <= decimals) {
261 return this;
262 }
263 const factor = FixedNumber.from("1" + zeros.substring(0, decimals), this.format);
264 const bump = BUMP.toFormat(this.format);
265 return this.mulUnsafe(factor).addUnsafe(bump).floor().divUnsafe(factor);
266 }
267 isZero() {
268 return (this._value === "0.0" || this._value === "0");
269 }
270 isNegative() {
271 return (this._value[0] === "-");
272 }
273 toString() { return this._value; }
274 toHexString(width) {
275 if (width == null) {
276 return this._hex;
277 }
278 if (width % 8) {
279 logger.throwArgumentError("invalid byte width", "width", width);
280 }
281 const hex = BigNumber.from(this._hex).fromTwos(this.format.width).toTwos(width).toHexString();
282 return hexZeroPad(hex, width / 8);
283 }
284 toUnsafeFloat() { return parseFloat(this.toString()); }
285 toFormat(format) {
286 return FixedNumber.fromString(this._value, format);
287 }
288 static fromValue(value, decimals, format) {
289 // If decimals looks more like a format, and there is no format, shift the parameters
290 if (format == null && decimals != null && !isBigNumberish(decimals)) {
291 format = decimals;
292 decimals = null;
293 }
294 if (decimals == null) {
295 decimals = 0;
296 }
297 if (format == null) {
298 format = "fixed";
299 }
300 return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format));
301 }
302 static fromString(value, format) {
303 if (format == null) {
304 format = "fixed";
305 }
306 const fixedFormat = FixedFormat.from(format);
307 const numeric = parseFixed(value, fixedFormat.decimals);
308 if (!fixedFormat.signed && numeric.lt(Zero)) {
309 throwFault("unsigned value cannot be negative", "overflow", "value", value);
310 }
311 let hex = null;
312 if (fixedFormat.signed) {
313 hex = numeric.toTwos(fixedFormat.width).toHexString();
314 }
315 else {
316 hex = numeric.toHexString();
317 hex = hexZeroPad(hex, fixedFormat.width / 8);
318 }
319 const decimal = formatFixed(numeric, fixedFormat.decimals);
320 return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat);
321 }
322 static fromBytes(value, format) {
323 if (format == null) {
324 format = "fixed";
325 }
326 const fixedFormat = FixedFormat.from(format);
327 if (arrayify(value).length > fixedFormat.width / 8) {
328 throw new Error("overflow");
329 }
330 let numeric = BigNumber.from(value);
331 if (fixedFormat.signed) {
332 numeric = numeric.fromTwos(fixedFormat.width);
333 }
334 const hex = numeric.toTwos((fixedFormat.signed ? 0 : 1) + fixedFormat.width).toHexString();
335 const decimal = formatFixed(numeric, fixedFormat.decimals);
336 return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat);
337 }
338 static from(value, format) {
339 if (typeof (value) === "string") {
340 return FixedNumber.fromString(value, format);
341 }
342 if (isBytes(value)) {
343 return FixedNumber.fromBytes(value, format);
344 }
345 try {
346 return FixedNumber.fromValue(value, 0, format);
347 }
348 catch (error) {
349 // Allow NUMERIC_FAULT to bubble up
350 if (error.code !== Logger.errors.INVALID_ARGUMENT) {
351 throw error;
352 }
353 }
354 return logger.throwArgumentError("invalid FixedNumber value", "value", value);
355 }
356 static isFixedNumber(value) {
357 return !!(value && value._isFixedNumber);
358 }
359}
360const ONE = FixedNumber.from(1);
361const BUMP = FixedNumber.from("0.5");
362//# sourceMappingURL=fixednumber.js.map
\No newline at end of file