maths.ts 7.3 KB

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