5
\$\begingroup\$

I know there are thousands of posts and questions around the topic of Float Discrepancy. And this is not really a question but a discussion for it.

After sitting repeatedly in front of the same problem, I made the following class to handle it once and for all of our projects. The direct value comparison is from the book The Art of Computer Programming by Donald E. Knuth. I just changed to unitys Mathf functionality.

So what is your take on the problem? Anything to add, something wrong?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class FloatDiscrepancy {
  public static float Accuracy = 0.00001f;

  public static float SqrAccuracy {
    get {
     return Accuracy * Accuracy;
    }
    private set { }
  }

  public static bool V4Equal(Vector4 a, Vector4 b){
    return Vector4.SqrMagnitude(a - b) < SqrAccuracy;
  }

  public static bool V3Equal(Vector3 a, Vector3 b){
    return Vector3.SqrMagnitude(a - b) < SqrAccuracy;
  }

  public static bool V2Equal (Vector2 a, Vector2 b){
    return Vector2.SqrMagnitude (a - b) < SqrAccuracy;
  }

  public static bool QuaternionEqual(Quaternion a, Quaternion b){
    return Quaternion.Angle (a, b) < Accuracy;
  }

  //Took the following from literature: The Art of Computer Programming by Donald E. Knuth
  public static bool approximatelyEqual(float a, float b){
    return Mathf.Abs(a - b) <= ( (Mathf.Abs(a) < Mathf.Abs(b) ? Mathf.Abs(b) : Mathf.Abs(a)) * Accuracy);
  }

  public static bool essentiallyEqual(float a, float b){
    return Mathf.Abs(a - b) <= ( (Mathf.Abs(a) > Mathf.Abs(b) ? Mathf.Abs(b) : Mathf.Abs(a)) * Accuracy);
  }

  public static bool definitelyGreaterThan(float a, float b){
    return (a - b) > ( (Mathf.Abs(a) < Mathf.Abs(b) ? Mathf.Abs(b) : Mathf.Abs(a)) * Accuracy);
  }

  public static bool definitelyLessThan(float a, float b){
    return (b - a) > ( (Mathf.Abs(a) < Mathf.Abs(b) ? Mathf.Abs(b) : Mathf.Abs(a)) * Accuracy);
  }
}
\$\endgroup\$
2
\$\begingroup\$

I can't quite figure out in which situation you would use approximatelyEqual() and in which essentiallyEqual(). and what the difference really should be.

I made some tests and overall it seems to work. I did though find the below strange behavior:

Testing with these two values:

    float a = 1.0f; 
    float b = 1.0f + 1e-6f;

    Console.WriteLine($"a: {a:F15}");
    Console.WriteLine($"b: {b:F15}");
    Console.WriteLine($"a - b: {a - b:F15}");
    Console.WriteLine($"b - a: {b - a:F15}");
    Console.WriteLine();

    Console.WriteLine($"approximatelyEqual: {approximatelyEqual(a, b)}");
    Console.WriteLine($"essentiallyEqual: {essentiallyEqual(a, b)}");
    Console.WriteLine($"definitelyGreaterThan: {definitelyGreaterThan(a, b)}");
    Console.WriteLine($"definitelyLessThan: {definitelyLessThan(a, b)}");
    Console.WriteLine();
    Console.WriteLine($"HEquals: {HEquals(a, b)}");
    Console.WriteLine($"HGreaterThan: {HGreaterThan(a, b)}");
    Console.WriteLine($"HLessThan: {HLessThan(a, b)}");

I get the expected output:

a: 1.000000000000000
b: 1.000001000000000
a - b: -0.000000953674300
b - a: 0.000000953674300

approximatelyEqual: True
essentiallyEqual: True
definitelyGreaterThan: False
definitelyLessThan: False

HEquals: True
HGreaterThan: False
HLessThan: False

But if I try this:

    float a = 0.0f; 
    float b = 1e-6f;

I get this:

a: 0.000000000000000
b: 0.000001000000000
a - b: -0.000001000000000
b - a: 0.000001000000000

approximatelyEqual: False
essentiallyEqual: False
definitelyGreaterThan: False
definitelyLessThan: True

HEquals: True
HGreaterThan: False
HLessThan: False

But I would have expected the same result?


The H-functions is my own naive implementations:

public static bool HEquals(float a, float b)
{
  return Math.Abs(a - b) < Accuracy;
}

public static bool HGreaterThan(float a, float b)
{
  return a - b >= Accuracy;
}

public static bool HLessThan(float a, float b)
{
  return a - b <= -Accuracy;
}

Disclaimer: I'm not using Unity3ds Mathf.Abs() but System.Math.Abs()

\$\endgroup\$
  • 1
    \$\begingroup\$ As I stated, I took the comparison functions straight out of the book. (Page 218) here is a link: github.com/djtrack16/thyme/blob/master/computer%20science/… Maybe it was over the top. For almost most cases your functions will work and are much cheaper. So to be complete I should add the functionality as it is, but I very much like the simpler approach. Maybe add both? \$\endgroup\$ – Binary Impact BIG Apr 6 '18 at 8:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.