fixednumber.js 14 KB

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