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 witherror: 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)
]
}
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
)
]
}
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()
)
]
}
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:
This is how the values are represented with expansion:
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
)
)
]
}
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 a2.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 ππ½