How to go deep in ethers one function at a time: ParseUnits()
Tags: ethers, bignumber, codereview
Published: 2022-01-10 18:57:58

Time to get familiar with your tool-set. Okay, so how does Ricmoo write his js/ts functions? Let us see ParseUnits().

It is written in Typescript at `ethers.js/packages/units/src.ts/index.ts`. Here it goes:

```
export function parseUnits(value: string, unitName?: BigNumberish): BigNumber {
    if (typeof(value) !== "string") {
        logger.throwArgumentError("value must be a string", "value", value);
    }
    if (typeof(unitName) === "string") {
        const index = names.indexOf(unitName);
        if (index !== -1) { unitName = 3 * index; }
    }
    return parseFixed(value, (unitName != null) ? unitName: 18);
}
```
The function takes two arguements and one is strigified value, and another 'unitname'. This function is kind of checker, meat is in 'parseFixed' function. And we see that this function is taken from:

`import { formatFixed, parseFixed } from "@ethersproject/bignumber";`


From 'bignumber', we get to know it is coming from 'fixednumber':

`export { formatFixed, FixedFormat, FixedNumber, parseFixed } from "./fixednumber";`

```
export function parseFixed(value: string, decimals?: BigNumberish): BigNumber {

    if (decimals == null) { decimals = 0; }
    const multiplier = getMultiplier(decimals);

    if (typeof(value) !== "string" || !value.match(/^-?[0-9.]+$/)) {
        logger.throwArgumentError("invalid decimal value", "value", value);
    }

    // Is it negative?
    const negative = (value.substring(0, 1) === "-");
    if (negative) { value = value.substring(1); }

    if (value === ".") {
        logger.throwArgumentError("missing value", "value", value);
    }

    // Split it into a whole and fractional part
    const comps = value.split(".");
    if (comps.length > 2) {
        logger.throwArgumentError("too many decimal points", "value", value);
    }

    let whole = comps[0], fraction = comps[1];
    if (!whole) { whole = "0"; }
    if (!fraction) { fraction = "0"; }

    // Trim trailing zeros
    while (fraction[fraction.length - 1] === "0") {
        fraction = fraction.substring(0, fraction.length - 1);
    }

    // Check the fraction doesn't exceed our decimals size
    if (fraction.length > multiplier.length - 1) {
        throwFault("fractional component exceeds decimals", "underflow", "parseFixed");
    }

    // If decimals is 0, we have an empty string for fraction
    if (fraction === "") { fraction = "0"; }

    // Fully pad the string with zeros to get to wei
    while (fraction.length < multiplier.length - 1) { fraction += "0"; }

    const wholeValue = BigNumber.from(whole);
    const fractionValue = BigNumber.from(fraction);

    let wei = (wholeValue.mul(multiplier)).add(fractionValue);

    if (negative) { wei = wei.mul(NegativeOne); }

    return wei;
}

```
Hope now you understand where is the real body after all the check. If no, here it is:

```
const wholeValue = BigNumber.from(whole);
    const fractionValue = BigNumber.from(fraction);

    let wei = (wholeValue.mul(multiplier)).add(fractionValue);

    if (negative) { wei = wei.mul(NegativeOne); }

    return wei;
```

Basically, Bignumber library is used to convert whole and fraction parts of value into into 'bignumber', and then they are added. So all ether calculation is 'wei' in ethers. 

But from where the 'Bignumber' is coming? It is coming from:

```
import { BigNumber, BigNumberish, isBigNumberish } from "./bignumber";
```

Lol, it is coming from 'bignumber'.

From there, we get to know it is wrapper around 'BN.js' library:

```
/**
 *  BigNumber
 *
 *  A wrapper around the BN.js object. We use the BN.js library
 *  because it is used by elliptic, so it is required regardless.
 *
 */

import _BN from "bn.js";
import BN = _BN.BN;
```
Do you still want to go deeper than that. Bravo. Here we go. The 'from' method is written by Ricmoo/team

```
 static from(value: any): BigNumber {
        if (value instanceof BigNumber) { return value; }

        if (typeof(value) === "string") {
            if (value.match(/^-?0x[0-9a-f]+$/i)) {
                return new BigNumber(_constructorGuard, toHex(value));
            }

            if (value.match(/^-?[0-9]+$/)) {
                return new BigNumber(_constructorGuard, toHex(new BN(value)));
            }

            return logger.throwArgumentError("invalid BigNumber string", "value", value);
        }
```

If you are still here, understand going below beyond this will push you outside the dev zone of application layer, you will go into decimal conversion mathematics. Your choose!

like it? Then, don't forget to give heart!