How do you prevent obscure bugs with Vector3.Distance and floating numbers?

I’ve just spent a week struggling with an obscure bug which I’ve found after intensive investigation.
( don’t break your head over it, I’ll point out the bug here below).

My function “checkVectorPosition” checks where a certain Vector ( = toCheck variable) lies in relation to VectorA and VectorB.

  • VectorA and VectorB lie on an imaginary infinite line where A to B dictates the “direction”.
  • This function tells me whether “toCheck” lies “behind” vectorA,
  • “inbetween” vectorA and vectorB,
  • or “infront” of vectorB.

Here is my code:

checkVectorPosition(vectorA: BABYLON.Vector3, vectorB: BABYLON.Vector3, toCheck: BABYLON.Vector3): string {

        const direction = BABYLON.Vector3.Normalize(vectorB.subtract(vectorA));
        const distanceToA = BABYLON.Vector3.Distance(vectorA, toCheck);
        const distanceToB = BABYLON.Vector3.Distance(vectorB, toCheck);
        const distanceAB = BABYLON.Vector3.Distance(vectorA, vectorB);

        if (BABYLON.Vector3.Dot(toCheck.subtract(vectorA), direction) < 0) {
            return "behind";
    // The following if statement is bugged
        } else if (distanceToA + distanceToB <= distanceAB) {
            return "inbetween";
        } else {
            return "infront";
        }
}

This works correctly most of the time, but on rare occasions, it returns the incorrect result.

Turns out, that in some rare cases, the calculation “distanceToA + distanceToB” is off by 0.00000000000003 due to the floating number stuff in JavaScript causing an otherwise “inbetween” to be returned as an “infront”.

I’ve solved this by rewriting the if-statement into:

if (distanceToA <= distanceAB && distanceToB <= distanceAB)

Now my question:

I’m afraid to run into similar bugs like these again somewhere in the future, losing valuable time.
What are your strategies and tips, to prevent these type of bugs from happening?

Hi @Gamedev
In cases with chance of small floating point errors, you can use a Epsilon (small offset value higher than the protential floating point error)

4 Likes

@Gamedev My understanding is that these aren’t bugs, but limitations of the underlying floating point hardware - there’s a finite range of numbers that can be accurately represented on a given platform. I would expect problems with a tolerance as small as 0.00000000000003.

I’m not sure what your use case is but have you looked at using floating origin to avoid such issues? Floating Origin (Huge Scenes Support) | Babylon.js Documentation

3 Likes

It was more of a general question asking for strategies to stop yourself from sneaking in bugs into your code related to floating point errors.

But what you are linking there is definetely interesting to learn about, I’ll look into this particular method.
Thanks for the information!

1 Like

Having dealt with JS floating point stuff in both financial and physics simulations, here are some strategies that I used:

  • use epsilon factors when comparing equality if you have to check equality, but…
  • avoid where possible logical test for equality (including <=). Any floating point equality test can be dicey, but it’s less so if you…
  • decide on a level of precision that you want and make sure that both sides of an operation conform to it. This will help give consistent results. Avoid where possible operations involving mis-matched levels of precision.
  • pre-compute results that are constant, e.g. a planets orbital characteristics, and use parameterized helper functions for dynamic values that leverage the constant ones. This helps consistency by ensuring that dynamic calcs are using the same figures across the board
  • scale and unscale values before and after an operation. This can be tricky, and lead to subtle bugs so it’s important to insulate and hide the scaling logic from the rest of your code
  • Avoid where possible operations that involve one or more numbers that are much bigger or much smaller than another one involved. For example, multiplying (short form for convenience sake) 1.37910^10 by 5.36544^2 will introduce variability in results, though not by as much as ofc the difference between 10^10 and 10^-10

HTH!

3 Likes