wharfkit / contract

Access table data, create actions, and retrieve types for Antelope-based Smart Contracts
https://wharfkit.com
Other
1 stars 1 forks source link

Number can only safely store up to 53 bits #69

Closed badgifter closed 5 months ago

badgifter commented 5 months ago

Hey,

Looping through rows of tokens and came across this token

{ "contract": "cmtokenomics", "symbol": "11,CMW" }
{ asset: 210494.76706034936 CMW }

When trying to do asset.value on this specific token, I get the Error: Number can only safely store up to 53 bits

at assert ( bn.js\lib\bn.js:6:21 ) at BN.toNumber ( bn.js\lib\bn.js:547:7 ) at Symbol.convertUnits ( @wharfkit+antelope@1.0.3\node_modules\@wharfkit\antelope\lib\antelope.js:2775:32 ) at get value [as value] ( @wharfkit\antelope\lib\antelope.js:2686:28 )

I looked up, seems to be a BN package issue when processing large numbers. Just curious if there is a better what to do the following...

I want to just get the price of this token, but in float64/double format since division with units cuts off most of the decimal.

cmw_asset.value / wax_asset.value won't work because of this conversion issue. ( who made a token with 11... come on )

aaroncox commented 5 months ago

I think this is probably a case where the Integer math provided by Wharf would be needed, since the number itself does fall outside of the scope of what JS can handle with native numbers.

Instead of using the .value on the asset, you can use the .unit value - which lines up with the underlying integer type that the blockchain is storing and actually using. These integer types all have additional math operations that you can perform on them using the bn.js package it uses.

https://wharfkit.com/docs/antelope/int#math

So in instances like this, instead of doing:

const price = cmw_asset.value / wax_asset.value

You can work with the units instead:

const price = cmw_asset.units.dividing(wax_asset.units)

The result is going to be an integer (representing the same value, but the decimal version multiplied by the precision). Meaning that 1.0000 in value is 10000 in units. From there you can divide it by the precision to get the decimal version back after performing the math, or you can cast it back to another asset using the Asset.fromUnits() static call.

const price = Asset.fromUnits(cmw_asset.units.dividing(wax_asset.units), '8,WAX')

Note the '8,WAX' there at the end is telling the fromUnits what precision and symbol to use for the new asset. I'm not sure what token your price is represented in, so that may need to be changed.

Hope that helps!

badgifter commented 5 months ago

Thanks for the response.

My first thought was exactly that. Just using the .units and .dividing to get the correct number. But I must be doing something wrong because the results I got with the dividing was unpredictable.

Result of BOPIXBO / COFFEE should be something around 8988.86263242

BOPIXBO.units.dividing( COFFEE.units ) returns -> Int64 { value: <BN: 231c> } Which in decimal format is 8988 ...

So its doing the division and returning with no precision. Both assets are 8 precision. The 1st example above was an 11 precision with 8 for the divider.

Am I doing something wrong to get it to return no precision?

I tried your Assets.fromUnits as well and it returned the following

0.00008988 COFFEE

aaroncox commented 5 months ago

Ah, yeah unfortunately the remainder is going to be lost there since it's a library that only supports integers, and any decimal values beyond that are dropped off. We did this because that's how the smart contract language itself operates to achieve consistency between the frontend and contracts.

I took the example numbers you provided above and ran a test to confirm what you were seeing.

const bopixbo = Asset.from('2057119233.65997982 BOPIXBO')
console.log('bopixbo', String(bopixbo.units))

const coffee = Asset.from('222871.66124201 COFFEE')
console.log('coffee ', String(coffee.units))

const result = bopixbo.units.dividing(coffee.units)
console.log('result ', String(result))

The output of which is:

bopixbo 205711923365997982
coffee  22287166124201
result  9230

Those unit values are the integer representation of the amount stored in the Asset (which is a UInt64). The result of 9230 is accurate, but the precision is gone. Just running it through a calculator shows the same, just with the precision.

2057119233.65997982 / 222871.66124201 = 9,230.06192082094

Sorry to have led you astray, but I think your original idea of pulling in a separate library might be required here if the precision is important for your use. Provided the library accepts string inputs for the values, you should be able to use String(asset.units) to pass in a the large integer, and then do the math with the library, to result in something that still retains the precision you're looking for.

One last note I guess though is that if this math is done at the smart contract level at any point, it's going to match the 9230 value without any precision I believe, since most (all?) assets in smart contracts are represented by these UInt64 values.

badgifter commented 5 months ago

Ok well, confirms my findings then.

Makes sense that we use units math to replicate the math in contract. Just in this instance, we just need a float for comparing price's across tokens and AMM pools.

Appreciate your time Aaron.