ethers.js/lib.esm/utils/maths.js

215 lines
6.9 KiB
JavaScript
Raw Permalink Normal View History

2022-11-30 15:44:23 -05:00
/**
* Some mathematic operations.
*
2022-12-02 21:27:06 -05:00
* @_subsection: api/utils:Math Helpers [about-maths]
2022-11-30 15:44:23 -05:00
*/
2022-09-05 16:57:11 -04:00
import { hexlify, isBytesLike } from "./data.js";
2022-11-30 15:44:23 -05:00
import { assert, assertArgument } from "./errors.js";
2022-09-05 16:57:11 -04:00
const BN_0 = BigInt(0);
const BN_1 = BigInt(1);
2022-12-09 18:24:58 -05:00
//const BN_Max256 = (BN_1 << BigInt(256)) - BN_1;
2022-09-15 22:58:45 -04:00
// IEEE 754 support 53-bits of mantissa
const maxValue = 0x1fffffffffffff;
2022-09-05 16:57:11 -04:00
/**
2022-11-30 15:44:23 -05:00
* Convert %%value%% from a twos-compliment representation of %%width%%
* bits to its value.
*
* If the highest bit is ``1``, the result will be negative.
2022-09-05 16:57:11 -04:00
*/
export function fromTwos(_value, _width) {
2022-12-09 18:24:58 -05:00
const value = getUint(_value, "value");
2022-09-15 22:58:45 -04:00
const width = BigInt(getNumber(_width, "width"));
2022-11-30 15:44:23 -05:00
assert((value >> width) === BN_0, "overflow", "NUMERIC_FAULT", {
operation: "fromTwos", fault: "overflow", value: _value
});
2022-09-05 16:57:11 -04:00
// Top bit set; treat as a negative value
if (value >> (width - BN_1)) {
const mask = (BN_1 << width) - BN_1;
return -(((~value) & mask) + BN_1);
}
return value;
}
/**
2022-11-30 15:44:23 -05:00
* Convert %%value%% to a twos-compliment representation of
* %%width%% bits.
*
* The result will always be positive.
2022-09-05 16:57:11 -04:00
*/
export function toTwos(_value, _width) {
2022-11-30 15:44:23 -05:00
let value = getBigInt(_value, "value");
2022-09-15 22:58:45 -04:00
const width = BigInt(getNumber(_width, "width"));
2022-11-30 15:44:23 -05:00
const limit = (BN_1 << (width - BN_1));
2022-09-05 16:57:11 -04:00
if (value < BN_0) {
2022-11-30 15:44:23 -05:00
value = -value;
assert(value <= limit, "too low", "NUMERIC_FAULT", {
operation: "toTwos", fault: "overflow", value: _value
});
2022-09-05 16:57:11 -04:00
const mask = (BN_1 << width) - BN_1;
2022-11-30 15:44:23 -05:00
return ((~value) & mask) + BN_1;
}
else {
assert(value < limit, "too high", "NUMERIC_FAULT", {
operation: "toTwos", fault: "overflow", value: _value
});
2022-09-05 16:57:11 -04:00
}
return value;
}
/**
* Mask %%value%% with a bitmask of %%bits%% ones.
*/
export function mask(_value, _bits) {
2022-12-09 18:24:58 -05:00
const value = getUint(_value, "value");
2022-09-15 22:58:45 -04:00
const bits = BigInt(getNumber(_bits, "bits"));
2022-09-05 16:57:11 -04:00
return value & ((BN_1 << bits) - BN_1);
}
2022-09-15 22:58:45 -04:00
/**
2023-02-12 22:14:26 -05:00
* Gets a BigInt from %%value%%. If it is an invalid value for
2022-09-15 22:58:45 -04:00
* a BigInt, then an ArgumentError will be thrown for %%name%%.
*/
export function getBigInt(value, name) {
switch (typeof (value)) {
case "bigint": return value;
case "number":
2022-11-09 02:57:02 -05:00
assertArgument(Number.isInteger(value), "underflow", name || "value", value);
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
2022-09-15 22:58:45 -04:00
return BigInt(value);
case "string":
try {
2022-11-30 15:44:23 -05:00
if (value === "") {
throw new Error("empty string");
}
2022-10-01 01:34:06 -04:00
if (value[0] === "-" && value[1] !== "-") {
return -BigInt(value.substring(1));
}
2022-09-15 22:58:45 -04:00
return BigInt(value);
}
catch (e) {
2022-11-09 02:57:02 -05:00
assertArgument(false, `invalid BigNumberish string: ${e.message}`, name || "value", value);
2022-09-15 22:58:45 -04:00
}
}
2022-11-09 02:57:02 -05:00
assertArgument(false, "invalid BigNumberish value", name || "value", value);
2022-09-15 22:58:45 -04:00
}
2023-06-01 17:52:58 -04:00
/**
* Returns %%value%% as a bigint, validating it is valid as a bigint
* value and that it is positive.
*/
2022-12-09 18:24:58 -05:00
export function getUint(value, name) {
const result = getBigInt(value, name);
2022-12-10 16:08:48 -05:00
assert(result >= BN_0, "unsigned value cannot be negative", "NUMERIC_FAULT", {
2022-12-09 18:24:58 -05:00
fault: "overflow", operation: "getUint", value
});
return result;
}
2022-09-15 22:58:45 -04:00
const Nibbles = "0123456789abcdef";
2022-09-05 16:57:11 -04:00
/*
* Converts %%value%% to a BigInt. If %%value%% is a Uint8Array, it
* is treated as Big Endian data.
*/
export function toBigInt(value) {
if (value instanceof Uint8Array) {
let result = "0x0";
for (const v of value) {
result += Nibbles[v >> 4];
result += Nibbles[v & 0x0f];
}
return BigInt(result);
}
2022-09-15 22:58:45 -04:00
return getBigInt(value);
}
/**
* Gets a //number// from %%value%%. If it is an invalid value for
* a //number//, then an ArgumentError will be thrown for %%name%%.
*/
export function getNumber(value, name) {
switch (typeof (value)) {
case "bigint":
2022-11-09 02:57:02 -05:00
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
2022-09-15 22:58:45 -04:00
return Number(value);
case "number":
2022-11-09 02:57:02 -05:00
assertArgument(Number.isInteger(value), "underflow", name || "value", value);
assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
2022-09-15 22:58:45 -04:00
return value;
case "string":
try {
2022-11-30 15:44:23 -05:00
if (value === "") {
throw new Error("empty string");
}
2022-09-15 22:58:45 -04:00
return getNumber(BigInt(value), name);
}
catch (e) {
2022-11-09 02:57:02 -05:00
assertArgument(false, `invalid numeric string: ${e.message}`, name || "value", value);
2022-09-15 22:58:45 -04:00
}
}
2022-11-09 02:57:02 -05:00
assertArgument(false, "invalid numeric value", name || "value", value);
2022-09-05 16:57:11 -04:00
}
2022-11-30 15:44:23 -05:00
/**
* Converts %%value%% to a number. If %%value%% is a Uint8Array, it
* is treated as Big Endian data. Throws if the value is not safe.
2022-09-05 16:57:11 -04:00
*/
export function toNumber(value) {
2022-09-15 22:58:45 -04:00
return getNumber(toBigInt(value));
2022-09-05 16:57:11 -04:00
}
/**
* Converts %%value%% to a Big Endian hexstring, optionally padded to
* %%width%% bytes.
*/
2022-12-09 18:24:58 -05:00
export function toBeHex(_value, _width) {
const value = getUint(_value, "value");
2022-09-05 16:57:11 -04:00
let result = value.toString(16);
if (_width == null) {
// Ensure the value is of even length
if (result.length % 2) {
result = "0" + result;
}
}
else {
2022-09-15 22:58:45 -04:00
const width = getNumber(_width, "width");
2023-11-01 16:17:49 -04:00
assert(width * 2 >= result.length, `value exceeds width (${width} bytes)`, "NUMERIC_FAULT", {
2022-12-10 16:08:48 -05:00
operation: "toBeHex",
fault: "overflow",
value: _value
});
2022-09-05 16:57:11 -04:00
// Pad the value to the required width
while (result.length < (width * 2)) {
result = "0" + result;
}
}
return "0x" + result;
}
/**
* Converts %%value%% to a Big Endian Uint8Array.
*/
2022-12-09 18:24:58 -05:00
export function toBeArray(_value) {
const value = getUint(_value, "value");
2022-09-05 16:57:11 -04:00
if (value === BN_0) {
return new Uint8Array([]);
}
let hex = value.toString(16);
if (hex.length % 2) {
hex = "0" + hex;
}
const result = new Uint8Array(hex.length / 2);
for (let i = 0; i < result.length; i++) {
const offset = i * 2;
result[i] = parseInt(hex.substring(offset, offset + 2), 16);
}
return result;
}
2022-09-15 22:58:45 -04:00
/**
* Returns a [[HexString]] for %%value%% safe to use as a //Quantity//.
*
* A //Quantity// does not have and leading 0 values unless the value is
* the literal value `0x0`. This is most commonly used for JSSON-RPC
* numeric values.
*/
2022-09-05 16:57:11 -04:00
export function toQuantity(value) {
2022-12-09 18:24:58 -05:00
let result = hexlify(isBytesLike(value) ? value : toBeArray(value)).substring(2);
2023-02-18 22:18:42 -05:00
while (result.startsWith("0")) {
2022-09-05 16:57:11 -04:00
result = result.substring(1);
}
if (result === "") {
result = "0";
}
return "0x" + result;
}
//# sourceMappingURL=maths.js.map