maths.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /**
  2. * Some mathematic operations.
  3. *
  4. * @_subsection: api/utils:Math Helpers [about-maths]
  5. */
  6. import { hexlify, isBytesLike } from "./data.js";
  7. import { assert, assertArgument } from "./errors.js";
  8. const BN_0 = BigInt(0);
  9. const BN_1 = BigInt(1);
  10. //const BN_Max256 = (BN_1 << BigInt(256)) - BN_1;
  11. // IEEE 754 support 53-bits of mantissa
  12. const maxValue = 0x1fffffffffffff;
  13. /**
  14. * Convert %%value%% from a twos-compliment representation of %%width%%
  15. * bits to its value.
  16. *
  17. * If the highest bit is ``1``, the result will be negative.
  18. */
  19. export function fromTwos(_value, _width) {
  20. const value = getUint(_value, "value");
  21. const width = BigInt(getNumber(_width, "width"));
  22. assert((value >> width) === BN_0, "overflow", "NUMERIC_FAULT", {
  23. operation: "fromTwos", fault: "overflow", value: _value
  24. });
  25. // Top bit set; treat as a negative value
  26. if (value >> (width - BN_1)) {
  27. const mask = (BN_1 << width) - BN_1;
  28. return -(((~value) & mask) + BN_1);
  29. }
  30. return value;
  31. }
  32. /**
  33. * Convert %%value%% to a twos-compliment representation of
  34. * %%width%% bits.
  35. *
  36. * The result will always be positive.
  37. */
  38. export function toTwos(_value, _width) {
  39. let value = getBigInt(_value, "value");
  40. const width = BigInt(getNumber(_width, "width"));
  41. const limit = (BN_1 << (width - BN_1));
  42. if (value < BN_0) {
  43. value = -value;
  44. assert(value <= limit, "too low", "NUMERIC_FAULT", {
  45. operation: "toTwos", fault: "overflow", value: _value
  46. });
  47. const mask = (BN_1 << width) - BN_1;
  48. return ((~value) & mask) + BN_1;
  49. }
  50. else {
  51. assert(value < limit, "too high", "NUMERIC_FAULT", {
  52. operation: "toTwos", fault: "overflow", value: _value
  53. });
  54. }
  55. return value;
  56. }
  57. /**
  58. * Mask %%value%% with a bitmask of %%bits%% ones.
  59. */
  60. export function mask(_value, _bits) {
  61. const value = getUint(_value, "value");
  62. const bits = BigInt(getNumber(_bits, "bits"));
  63. return value & ((BN_1 << bits) - BN_1);
  64. }
  65. /**
  66. * Gets a BigInt from %%value%%. If it is an invalid value for
  67. * a BigInt, then an ArgumentError will be thrown for %%name%%.
  68. */
  69. export function getBigInt(value, name) {
  70. switch (typeof (value)) {
  71. case "bigint": return value;
  72. case "number":
  73. assertArgument(Number.isInteger(value), "underflow", name || "value", value);
  74. assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
  75. return BigInt(value);
  76. case "string":
  77. try {
  78. if (value === "") {
  79. throw new Error("empty string");
  80. }
  81. if (value[0] === "-" && value[1] !== "-") {
  82. return -BigInt(value.substring(1));
  83. }
  84. return BigInt(value);
  85. }
  86. catch (e) {
  87. assertArgument(false, `invalid BigNumberish string: ${e.message}`, name || "value", value);
  88. }
  89. }
  90. assertArgument(false, "invalid BigNumberish value", name || "value", value);
  91. }
  92. /**
  93. * Returns %%value%% as a bigint, validating it is valid as a bigint
  94. * value and that it is positive.
  95. */
  96. export function getUint(value, name) {
  97. const result = getBigInt(value, name);
  98. assert(result >= BN_0, "unsigned value cannot be negative", "NUMERIC_FAULT", {
  99. fault: "overflow", operation: "getUint", value
  100. });
  101. return result;
  102. }
  103. const Nibbles = "0123456789abcdef";
  104. /*
  105. * Converts %%value%% to a BigInt. If %%value%% is a Uint8Array, it
  106. * is treated as Big Endian data.
  107. */
  108. export function toBigInt(value) {
  109. if (value instanceof Uint8Array) {
  110. let result = "0x0";
  111. for (const v of value) {
  112. result += Nibbles[v >> 4];
  113. result += Nibbles[v & 0x0f];
  114. }
  115. return BigInt(result);
  116. }
  117. return getBigInt(value);
  118. }
  119. /**
  120. * Gets a //number// from %%value%%. If it is an invalid value for
  121. * a //number//, then an ArgumentError will be thrown for %%name%%.
  122. */
  123. export function getNumber(value, name) {
  124. switch (typeof (value)) {
  125. case "bigint":
  126. assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
  127. return Number(value);
  128. case "number":
  129. assertArgument(Number.isInteger(value), "underflow", name || "value", value);
  130. assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
  131. return value;
  132. case "string":
  133. try {
  134. if (value === "") {
  135. throw new Error("empty string");
  136. }
  137. return getNumber(BigInt(value), name);
  138. }
  139. catch (e) {
  140. assertArgument(false, `invalid numeric string: ${e.message}`, name || "value", value);
  141. }
  142. }
  143. assertArgument(false, "invalid numeric value", name || "value", value);
  144. }
  145. /**
  146. * Converts %%value%% to a number. If %%value%% is a Uint8Array, it
  147. * is treated as Big Endian data. Throws if the value is not safe.
  148. */
  149. export function toNumber(value) {
  150. return getNumber(toBigInt(value));
  151. }
  152. /**
  153. * Converts %%value%% to a Big Endian hexstring, optionally padded to
  154. * %%width%% bytes.
  155. */
  156. export function toBeHex(_value, _width) {
  157. const value = getUint(_value, "value");
  158. let result = value.toString(16);
  159. if (_width == null) {
  160. // Ensure the value is of even length
  161. if (result.length % 2) {
  162. result = "0" + result;
  163. }
  164. }
  165. else {
  166. const width = getNumber(_width, "width");
  167. assert(width * 2 >= result.length, `value exceeds width (${width} bytes)`, "NUMERIC_FAULT", {
  168. operation: "toBeHex",
  169. fault: "overflow",
  170. value: _value
  171. });
  172. // Pad the value to the required width
  173. while (result.length < (width * 2)) {
  174. result = "0" + result;
  175. }
  176. }
  177. return "0x" + result;
  178. }
  179. /**
  180. * Converts %%value%% to a Big Endian Uint8Array.
  181. */
  182. export function toBeArray(_value) {
  183. const value = getUint(_value, "value");
  184. if (value === BN_0) {
  185. return new Uint8Array([]);
  186. }
  187. let hex = value.toString(16);
  188. if (hex.length % 2) {
  189. hex = "0" + hex;
  190. }
  191. const result = new Uint8Array(hex.length / 2);
  192. for (let i = 0; i < result.length; i++) {
  193. const offset = i * 2;
  194. result[i] = parseInt(hex.substring(offset, offset + 2), 16);
  195. }
  196. return result;
  197. }
  198. /**
  199. * Returns a [[HexString]] for %%value%% safe to use as a //Quantity//.
  200. *
  201. * A //Quantity// does not have and leading 0 values unless the value is
  202. * the literal value `0x0`. This is most commonly used for JSSON-RPC
  203. * numeric values.
  204. */
  205. export function toQuantity(value) {
  206. let result = hexlify(isBytesLike(value) ? value : toBeArray(value)).substring(2);
  207. while (result.startsWith("0")) {
  208. result = result.substring(1);
  209. }
  210. if (result === "") {
  211. result = "0";
  212. }
  213. return "0x" + result;
  214. }
  215. //# sourceMappingURL=maths.js.map