All files / ethers.js/src.ts/utils maths.ts

94.98% Statements 246/259
89.83% Branches 53/59
100% Functions 11/11
94.98% Lines 246/259

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 2601x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1339x 1339x 1339x 1339x 1339x 1339x 1339x 1339x 1339x 589x 589x 589x 750x 750x 750x 1x 1x 1x 1x 1x 1x 1x 1x 3199x 3199x 3199x 3199x 3199x 3199x 649x 649x 649x 649x 649x 649x 3199x 2550x 2550x 2550x 2550x 2550x 2550x 2550x 1x 1x 1x 1x 1x 7700x 7700x 7700x 7700x 1x 1x 1x 1x 1x 1x 3174108x 3174108x 3174108x 430478x 430478x 430478x 3174108x 488949x 488949x 488949x 641x 641x 488307x 488949x 3x 3x 3174108x 3174108x 3174108x 1x 1x 1x 1x 1x 1x 1498544x 1498544x 1498544x 1498544x 1498544x 1498544x 1x 1x 1x 1x 1x 1x 1x 1x 42635x 42635x 42635x 2264320x 2264320x 2264320x 42635x 42635x       1x 1x 1x 1x 1x 1x 251374x 251374x 45454x 45454x 251374x 168182x 168182x 168182x 251374x 37737x 37737x 37736x 37737x 3x 3x 251374x 251374x 251374x 1x 1x 1x 1x 1x 1x 1x 7338x 7338x 1x 1x 1x 1x 1x 1x 36011x 36011x 36011x 36011x 36011x 1x 1x 36011x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 36009x 1x 1x 1x 1x 1x 1453494x 1453494x 1453494x 523504x 523504x 523504x 929989x 929989x 1453494x 929989x 1453494x                     929989x 929989x 1453494x 11189112x 11189112x 11189112x 929989x 929989x 929989x 1x 1x 1x 1x 1x 1x 1x 1x 1x 123x 123x 123x 123x 123x  
/**
 *  Some mathematic operations.
 *
 *  @_subsection: api/utils:Math Helpers  [about-maths]
 */
import { hexlify, isBytesLike } from "./data.js";
import { assert, assertArgument } from "./errors.js";
 
import type { BytesLike } from "./data.js";
 
/**
 *  Any type that can be used where a numeric value is needed.
 */
export type Numeric = number | bigint;
 
/**
 *  Any type that can be used where a big number is needed.
 */
export type BigNumberish = string | Numeric;
 
 
const BN_0 = BigInt(0);
const BN_1 = BigInt(1);
 
//const BN_Max256 = (BN_1 << BigInt(256)) - BN_1;
 
 
// IEEE 754 support 53-bits of mantissa
const maxValue = 0x1fffffffffffff;
 
/**
 *  Convert %%value%% from a twos-compliment representation of %%width%%
 *  bits to its value.
 *
 *  If the highest bit is ``1``, the result will be negative.
 */
export function fromTwos(_value: BigNumberish, _width: Numeric): bigint {
    const value = getUint(_value, "value");
    const width = BigInt(getNumber(_width, "width"));
 
    assert((value >> width) === BN_0, "overflow", "NUMERIC_FAULT", {
        operation: "fromTwos", fault: "overflow", value: _value
    });
 
    // 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;
}
 
/**
 *  Convert %%value%% to a twos-compliment representation of
 *  %%width%% bits.
 *
 *  The result will always be positive.
 */
export function toTwos(_value: BigNumberish, _width: Numeric): bigint {
    let value = getBigInt(_value, "value");
    const width = BigInt(getNumber(_width, "width"));
 
    const limit = (BN_1 << (width - BN_1));
 
    if (value < BN_0) {
        value = -value;
        assert(value <= limit, "too low", "NUMERIC_FAULT", {
            operation: "toTwos", fault: "overflow", value: _value
        });
        const mask = (BN_1 << width) - BN_1;
        return ((~value) & mask) + BN_1;
    } else {
        assert(value < limit, "too high", "NUMERIC_FAULT", {
            operation: "toTwos", fault: "overflow", value: _value
        });
    }
 
    return value;
}
 
/**
 *  Mask %%value%% with a bitmask of %%bits%% ones.
 */
export function mask(_value: BigNumberish, _bits: Numeric): bigint {
    const value = getUint(_value, "value");
    const bits = BigInt(getNumber(_bits, "bits"));
    return value & ((BN_1 << bits) - BN_1);
}
 
/**
 *  Gets a BigInt from %%value%%. If it is an invalid value for
 *  a BigInt, then an ArgumentError will be thrown for %%name%%.
 */
export function getBigInt(value: BigNumberish, name?: string): bigint {
    switch (typeof(value)) {
        case "bigint": return value;
        case "number":
            assertArgument(Number.isInteger(value), "underflow", name || "value", value);
            assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
            return BigInt(value);
        case "string":
            try {
                if (value === "") { throw new Error("empty string"); }
                if (value[0] === "-" && value[1] !== "-") {
                    return -BigInt(value.substring(1));
                }
                return BigInt(value);
            } catch(e: any) {
                assertArgument(false, `invalid BigNumberish string: ${ e.message }`, name || "value", value);
            }
    }
    assertArgument(false, "invalid BigNumberish value", name || "value", value);
}
 
/**
 *  Returns %%value%% as a bigint, validating it is valid as a bigint
 *  value and that it is positive.
 */
export function getUint(value: BigNumberish, name?: string): bigint {
    const result = getBigInt(value, name);
    assert(result >= BN_0, "unsigned value cannot be negative", "NUMERIC_FAULT", {
        fault: "overflow", operation: "getUint", value
    });
    return result;
}
 
const Nibbles = "0123456789abcdef";
 
/*
 * Converts %%value%% to a BigInt. If %%value%% is a Uint8Array, it
 * is treated as Big Endian data.
 */
export function toBigInt(value: BigNumberish | Uint8Array): bigint {
    if (value instanceof Uint8Array) {
        let result = "0x0";
        for (const v of value) {
            result += Nibbles[v >> 4];
            result += Nibbles[v & 0x0f];
        }
        return BigInt(result);
    }

    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: BigNumberish, name?: string): number {
    switch (typeof(value)) {
        case "bigint":
            assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
            return Number(value);
        case "number":
            assertArgument(Number.isInteger(value), "underflow", name || "value", value);
            assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value);
            return value;
        case "string":
            try {
                if (value === "") { throw new Error("empty string"); }
                return getNumber(BigInt(value), name);
            } catch(e: any) {
                assertArgument(false, `invalid numeric string: ${ e.message }`, name || "value", value);
            }
    }
    assertArgument(false, "invalid numeric value", name || "value", value);
}
 
 
/**
 *  Converts %%value%% to a number. If %%value%% is a Uint8Array, it
 *  is treated as Big Endian data. Throws if the value is not safe.
 */
export function toNumber(value: BigNumberish | Uint8Array): number {
    return getNumber(toBigInt(value));
}
 
/**
 *  Converts %%value%% to a Big Endian hexstring, optionally padded to
 *  %%width%% bytes.
 */
export function toBeHex(_value: BigNumberish, _width?: Numeric): string {
    const value = getUint(_value, "value");
 
    let result = value.toString(16);
 
    if (_width == null) {
        // Ensure the value is of even length
        if (result.length % 2) { result = "0" + result; }
    } else {
        const width = getNumber(_width, "width");
 
        // Special case when both value and width are 0 (see: #5025)
        if (width === 0 && value === BN_0) { return "0x"; }
 
        assert(width * 2 >= result.length, `value exceeds width (${ width } bytes)`, "NUMERIC_FAULT", {
            operation: "toBeHex",
            fault: "overflow",
            value: _value
        });
 
        // 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.
 */
export function toBeArray(_value: BigNumberish, _width?: Numeric): Uint8Array {
    const value = getUint(_value, "value");
 
    if (value === BN_0) {
        const width = (_width != null) ? getNumber(_width, "width"): 0;
        return new Uint8Array(width);
    }
 
    let hex = value.toString(16);
    if (hex.length % 2) { hex = "0" + hex; }
 
    if (_width != null) {
        const width = getNumber(_width, "width");

        while (hex.length < (width * 2)) { hex = "00" + hex; }

        assert((width * 2) === hex.length, `value exceeds width (${ width } bytes)`, "NUMERIC_FAULT", {
            operation: "toBeArray",
            fault: "overflow",
            value: _value
        });
    }
 
    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;
}
 
/**
 *  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.
 */
export function toQuantity(value: BytesLike | BigNumberish): string {
    let result = hexlify(isBytesLike(value) ? value: toBeArray(value)).substring(2);
    while (result.startsWith("0")) { result = result.substring(1); }
    if (result === "") { result = "0"; }
    return "0x" + result;
}