Open jaekwon opened 1 year ago
I Did some research, and Go is using the IEEE 754 standard for floats:
Also, I tried with different CPU architectures and go versions checking the binary representation, which was always the same.
Protobuf, and by extension amino, are encoding floats using little-endian.
To provide some context on this issue, see this article.
We solve a lot of headaches by not allowing float multiply-accumulate within Gno already. That seems to be a great cause of pain in floating point determinism, particularly because x86 seems to have 80-bit precision[^1] for intermediate results.
In order to make this issue actionable, I propose the following tasks:
gnovm/pkg/gnolang/floats
, which contains source code for the main algebraic operators (Add
, Multiply
, Subtract
, Divide
) and implements them in assembly, trying to copy over from Go code, or Go assembly code in general.
[^1]: See http://yosefk.com/blog/consistency-how-to-defeat-the-purpose-of-ieee-floating-point.html.
So, working on #1153, go 1.21 modifies this function and adds this interesting comment:
func isOddInt(x float64) bool {
if Abs(x) >= (1 << 53) {
// 1 << 53 is the largest exact integer in the float64 format.
// Any number outside this range will be truncated before the decimal point and therefore will always be
// an even integer.
// Without this check and if x overflows int64 the int64(xi) conversion below may produce incorrect results
// on some architectures (and does so on arm64). See issue #57465.
return false
}
xi, xf := Modf(x)
return xf == 0 && int64(xi)&1 == 1
}
I'm slowly starting to think that if we want to have floating points in Gno, they should have a software implementation which uses no hardware floating points under the hood...
https://github.com/gnolang/gno/pull/306 introduced floating point and decimals, but it isn't clear if it actually is deterministic across go versions and architectures. This Issue should remain open until genesis, to ensure that the genesis validators can stay in sync.