Godot version:
v3.1.1.stable.official (64-bit)
v3.2.alpha3.official (64-bit)
OS/device including version:
GNU/Linux: Lubuntu 19.10, LinuxMint 19.2
Issue description:
Accessing 'x' (or 'y') property of Vector2 gets unexpected float point number precision. Maybe it is similar to #27665
Steps to reproduce:
const EPSILON := 0.000001 # produces bug
#const EPSILON := 0.00001 # produces bug
#const EPSILON := 0.0001 # produces bug
#const EPSILON := 0.001 # produces bug
#const EPSILON := 0.01 # produces bug
#const EPSILON := 0.1 # produces bug
func _ready() -> void:
var v := Vector2(EPSILON, 0.0)
assert(String(v.x) == String(EPSILON))
assert(v.x == EPSILON) # The bug appears here
Minimal reproduction project:
In details
BUG-DEMO.zip
Vector is single precision, Variant is double, neither can represent 0.000001
exactly.
This is not a bug, simply doing ==
is not a correct way to compare floats, there's is-equal-approx function to do it.
To clarify what's going on under the hood, 0.000001
looks nice and small in decimal, but floats work in binary. And in binary 0.000001
is infinite fraction:
0.0000000000000000000100001100011011110111101000001011010111101101100011010011011010110100......
(-> 20 places) 00001100011011110111101 (single)
(-> 20 places) 0000110001101111011110100000101101011110110110001101 (double)
single
use 23 bits for mantissa and will store only this part as:2^(-20) * 1.00001100011011110111101
= 0.0000009999999974752427078783512115478515625
double
use 52 bits and will be:2^(-20) * 1.0000110001101111011110100000101101011110110110001101
= 0.000000999999999999999954748111825886258685613938723690807819366455078125
0.000000000000002524757246869760614338407123113938723690807819366455078125
That's why ==
operator will return false
.
Might it be better to allow for the error inherent in the comparison specifically when checking for equality between floats of different precision, or might that be too complicated/inefficient to be practical?
@name-here Making ==
use is_equal_approx()
behind the scenes would adversely affect performance, see https://github.com/godotengine/godot/pull/18992#issuecomment-536223410.
Ok, I am understanding that ==
is not best way to compare float point numbers, but look
var epsilons := [
0.000001,
0.00001,
0.0001,
0.001,
0.01,
0.1,
1.0
]
func _ready() -> void:
for EPSILON in epsilons:
var v := Vector2(EPSILON, 0.0)
var result: bool = (abs(v.x) + abs(v.y)) < EPSILON # Must be 'false' always
print_debug("\nabs({0}) + abs({1}) < {2} is {3}".format([
v.x,
v.y,
EPSILON,
result
]))
This example does not use ==
. I expect same boolean results in all cases, but Godot thinks different.
So I have to remember about inner representation of two different Godot's float
. It is not big deal but discouraging.
UPD:
Using Variant. Same thing.
var epsilons := [
0.000001,
0.00001,
0.0001,
0.001,
0.01,
0.1,
1.0
]
func _ready() -> void:
for EPSILON in epsilons:
var v := Vector2(EPSILON, 0.0)
var vx = v.x # Variant-type
var vy = v.y # Variant-type, and EPSILON is Variant-type too
##
#var vx = EPSILON # OK because pure value without type casting
#var vy = 0.0 # OK
##
var result: bool = (abs(vx) + abs(vy)) < EPSILON # Must be 'false' always
print_debug("\nabs({0}) + abs({1}) < {2} is {3}".format([
vx,
vy,
EPSILON,
result
]))
This is not Godot's floats, that's how IEEE 754 (used in almost all hardware) floats works.
Same code in C
will have same result.
C sample
Code:
#include <stdio.h>
int main() {
double epsilons[] = {
0.000001,
0.00001,
0.0001,
0.001,
0.01,
0.1,
1.0
};
for (int i = 0; i < 7; i++) {
printf("%f < %f is %s\n", (float)epsilons[i], epsilons[i], ((float)epsilons[i] < epsilons[i]) ? "T" : "F");
}
}
Output:
0.000001 < 0.000001 is T
0.000010 < 0.000010 is T
0.000100 < 0.000100 is T
0.001000 < 0.001000 is F
0.010000 < 0.010000 is T
0.100000 < 0.100000 is F
1.000000 < 1.000000 is F
Floating-point math is somewhat counter-intuitive in general, you should always assume that most numbers end up being slightly imprecise. Every language using standard floats will have similar issues: https://0.30000000000000004.com/
Thank you, @bruvzg, and everyone for explanation. I just forgot about abs(a - b) < EPSILON
and is-equal-approx
function.
Sorry, I ate your time.
abs(a - b) < EPSILON
with fixed EPSILON it not the best way to compare floats ether, EPSILON should be relative, see this article - Comparing floating-point numbers, 2012 Edition for more info. (AFAIK is_equal_approx
works exactly like this)
Most helpful comment
abs(a - b) < EPSILON
with fixed EPSILON it not the best way to compare floats ether, EPSILON should be relative, see this article - Comparing floating-point numbers, 2012 Edition for more info. (AFAIKis_equal_approx
works exactly like this)