Ethers.js: Discussion: Support scientific notation in BigNumbers?

Created on 12 Jul 2018  ·  11Comments  ·  Source: ethers-io/ethers.js

This came up recently, and so I thought I would open it up for discussion.

Pros:

  • Very common values are easier and less prone to bugs to enter (e.g. "1e18"); these are already available as ethers.constants.WeiPerEther, but other common values may come up too, such as 1e9 referring to wei per gwei.
  • Nice feature to have

Cons:

  • Yes another way string values can be confused for hexadecimal values (1e18 is 7704 in hex); this may be less an issue, since hexadecimal strings must begin with a 0x prefix, and currently this is already a problem if people try 19, meaning 0x19, which is 25
  • This might encourage the use of scientific notation in newer developers, which would be confused by the numeric overflow; for example bigNumberify((1e18 + 65) - (1e18 + 64)); the underflow occurs before we have a chance to catch it; this is already still an issue though
  • This may require slightly more complicated code than is immediately obvious and will require substantial testing

Anyways, please anyone else please feel free to add, augment or refute any points.

enhancement major-bump

Most helpful comment

It might be worthwhile to add support for scientific notation as it is currently being recommended in the "URL Format for Transaction Requests" ERC681 format.

The use of scientific notation is strongly encouraged. For example, requesting 2.014 ETH to address 0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359 would look as follows: ethereum:0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359?value=2.014e18

Depending on the adoption of the specification it would be great to easily convert the value using the ethers.utils library when processing QR codes that use that specification without creating an additional helper function.

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-681.md

All 11 comments

I think it's not necessary and might just confuse people. There is already stuff like utils.parseUnits that can take any "real world number" and convert it to an "ethereum number", i.e., times 10^18. I've seen some helper functions around that are essentially two methods toBaseUnitAmount and fromBaseUnitAmount where you specify the "real world number" and the decimals and converts it to and from the "ethereum number".

For instance, here are two great functions from the 0x wrapper: https://github.com/0xProject/0x-monorepo/blob/development/packages/web3-wrapper/src/web3_wrapper.ts

   /**
     * A unit amount is defined as the amount of a token above the specified decimal places (integer part).
     * E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent
     * to 1 unit.
     * @param   amount      The amount in baseUnits that you would like converted to units.
     * @param   decimals    The number of decimal places the unit amount has.
     * @return  The amount in units.
     */
    public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
        assert.isValidBaseUnitAmount('amount', amount);
        assert.isNumber('decimals', decimals);
        const aUnit = new BigNumber(BASE_TEN).pow(decimals);
        const unit = amount.div(aUnit);
        return unit;
    }
    /**
     * A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits
     * is the amount expressed in the smallest denomination.
     * E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000
     * @param   amount      The amount of units that you would like converted to baseUnits.
     * @param   decimals    The number of decimal places the unit amount has.
     * @return  The amount in baseUnits.
     */
    public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
        assert.isBigNumber('amount', amount);
        assert.isNumber('decimals', decimals);
        const unit = new BigNumber(BASE_TEN).pow(decimals);
        const baseUnitAmount = amount.times(unit);
        const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
        if (hasDecimals) {
            throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`);
        }
        return baseUnitAmount;
    }

I think these kinds of methods with some extra functionality like common exponentiations solve a lot of the use cases.

One thing I think would be great is a rand() function. This exists in bignumber.js but unfortunately not in bn.js, and since it's pretty hard to get at in this library, a top level function random function would be nice for generating stuff like salts.

I agree with you, but someone asked, so I thought I'd put it up for discussion. I like to get consensus and not just make broad-sweeping decisions. :)

For your random number, you could use:

var randomNumber = ethers.utils.bigNumberify(ethers.utils.randomBytes(32));

Or for salts, which are usually just bytes32:

var salt = ethers.utils.randomBytes(32);

:)

Wow.... that is too easy. Thank you.

I think we have reached a consensus (an my personal preference) to not support scientific notation in the BigNumber class.

Closing this issue, but if anyone wants to re-open it, please feel free to do so. :)

It might be worthwhile to add support for scientific notation as it is currently being recommended in the "URL Format for Transaction Requests" ERC681 format.

The use of scientific notation is strongly encouraged. For example, requesting 2.014 ETH to address 0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359 would look as follows: ethereum:0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359?value=2.014e18

Depending on the adoption of the specification it would be great to easily convert the value using the ethers.utils library when processing QR codes that use that specification without creating an additional helper function.

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-681.md

Excellent point. :)

I’ll look more into that, thanks.

(Re-opening)

(The above commit reference is wrong; it was meant to address #301)

Any news on the adoption of EIP-681?

I think this might make sense to add as a static method to the new FixedNumber object regardless, rather than allow scientific notation in arbitrary places.

Also, as I try to implement this I notice the specification has unbalanced brackets... :s

I'm guessing:

[ ( "e" / "E" ) [ 1*DIGIT ] [ "+" UNIT ]

was meant to be something like:

[ ( "e" / "E" ) [ 1*DIGIT ] | [ "+" UNIT ] ]

but it still doesn't fully make sense to me. For now I'll ignore units. It will be backwards compatible once the spec is corrected.

I am having trouble converting from bignumber.js to ethers.BigNumber for values above e21 - the ability to parse scientific numbers would be a workaround for direct casting. Any other solutions?

image

Ah - it is possible to prevent exponentiation of bignumber.js by calling .string(10)

Was this page helpful?
0 / 5 - 0 ratings