MaxGraey / as-bignum

Fixed length big numbers for AssemblyScript πŸš€

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Conversion of large f64 float values to u128 fails.

cristobal opened this issue Β· comments

Describe the bug
Conversion of large positive f64 numbers to u128 fails with e.g.:

  • Doing u128.fromF64(value) fails with error: FunctionCallError(WasmTrap(IllegalArithmetic))
  • Doing u128.fromString(value.toString()) fails by converting to a simple digit number (does not take into the fraction nor exponent).

When the f64 value can be represented with an exponent e.g. 2.5e+23

To Reproduce

i have deployed the following smart contract to the following account f64-to-128.cristobal.testnet:

Small float values to u128 (OK)

Conversion for small values works fine e.g.:

export function small_values_ok():u128[] {
  return [
    u128.fromF64(parseFloat('10') as f64),
    u128.fromF64(parseFloat('100') as f64),
    u128.fromF64(parseFloat('1000') as f64),
    u128.fromF64(parseFloat('10000') as f64),
    u128.fromF64(parseFloat('100000') as f64),
    u128.fromF64(parseFloat('10000000') as f64)
  ]
}

Result from call:
Screenshot 2021-12-02 at 14 46 31

Large value float value to u128 (Fails):

Conversion from f64 value fails e.g:

export function large_values_from_f64_fails(): u128[] {
  return [
    u128.fromF64(
      parseFloat('1000000000000000000000000') as f64
    )
  ]
}

Screenshot 2021-12-02 at 14 56 13

Conversion from f64 string value also fails e.g.:

export function large_values_from_f64_string_fails(): u128[] {
  return [
    u128.fromString(
      (parseFloat('1000000000000000000000000') as f64).toString()
    )
  ]
}

Screenshot 2021-12-02 at 14 56 33

Large values works when expanding string to include trailing zeros.

Created a custom function that expands large float values that are represented with exponent e.g.:

function parseF64ValueToString(value: f64): string {
  const repr = value.toString();
  if (repr.indexOf('e+') < 0) {
    return repr;
  }


  const elements = repr.split('e+');
  const val = elements[0];

  const args = [
    val.charAt(0)
  ];

  let stop = val.indexOf('0') > 0
    ? val.indexOf('0')
    : val.length;

  for (let i = 2; i < stop; i++) {
    args.push(val.charAt(i));
  }

  const exp = parseInt(elements[1]) as i32;
  stop = exp - (args.length - 1);
  for (let i = 0; i < stop; i++) {
    args.push('0');
  }

  return args.join('');
}

This is how the values are represented without expansion:
Screenshot 2021-12-02 at 15 01 46

This is how the values are represented with expansion:
Screenshot 2021-12-02 at 15 01 32

We can see that converting the expanded functions works fine:

export function large_values_ok(): u128[] {
  return [
    u128.fromString(
      parseF64ValueToString(
        parseFloat('1000000000000000000000000') as f64
      )
    ),
    u128.fromString(
      parseF64ValueToString(
        parseFloat('2500000000000000000000000') as f64
      )
    )
  ]
}

Screenshot 2021-12-02 at 14 58 02

Expected behavior
Would expect that conversion for large float values to u128 should work as expected.

Perhaps the approach above could be a temporary solution until a proper approach is in place, i am no expert on web assembly and not sure how this should be done there.

Maybe some logic like this could be used here:

@inline
static fromF64(value: f64): u128 {
   if (value.toString().indexOf('e+')) {
      return fromString(parseF64ValueToString(value));
   }

   return new u128(<u64>value, reinterpret<i64>(value) >> 63);
}

If you're using NEAR or any other smart contracts you can't use any floating point arithmetic. It's ban on Wasm VM level and that's why you got WasmTrap(IllegalArithmetic) error which is VM error

If you're using NEAR or any other smart contracts you can't use any floating point arithmetic. It's ban on Wasm VM level and that's why you got WasmTrap(IllegalArithmetic) error which is VM error

I see so should this be rather be suggested to be a utility method to use in the near-sdk-as library instead?

You can't use f64 and f32 types in smart contracts. That's it. And I don't understand why you try to do this? Why parseFloat? u128.from('10000000') should work. If you dot a 2.5e+23 in input string you should normalize this manually.

You can't use f64 and f32 types in smart contracts. That's it. And I don't understand why you try to do this? Why parseFloat? u128.from('10000000') should work. If you dot a 2.5e+23 in input string you should normalize this manually.

Those were just examples nothing more, thanks for the clarification and explanation on how things work in NEAR smart contracts. Seems i'm not the only one confused here on this topic when it comes to floating point arithmetic in NEAR smart contracts, since many want to convert floating point near values to Yocto near which leaves a large floating point number.

Yes thats what my function was doing normalizing large positive floating points so that they could be read by the u128.fromString() method.

Will try avoiding using floating values at all in NEAR smart contracts in the future πŸ‘ŒπŸ½

Again thanks for your thourough explanation and time πŸ™πŸ½