Godot: Comparison of two float point numbers (as property of Vector2) takes opposite result in some case

Created on 31 Oct 2019  路  8Comments  路  Source: godotengine/godot

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

archived discussion core

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. (AFAIK is_equal_approx works exactly like this)

All 8 comments

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
  • Difference is:
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)

Was this page helpful?
0 / 5 - 0 ratings