6

I have just noticed that the following code returns true:

Mathf.Approximately(0.0f, float.Epsilon); // true

I have read the Mathf.Approximately Documentation and it states that:

Approximately() compares two floats and returns true if they are within a small value (Epsilon) of each other.

And Mathf.Epsilon Documentation states that:

  • anyValue + Epsilon = anyValue
  • anyValue - Epsilon = anyValue
  • 0 + Epsilon = Epsilon
  • 0 - Epsilon = -Epsilon

As a result, I ran the following code, expecting it to be false, but it also returns true.

Mathf.Approximately(0.0f, 2.0f * float.Epsilon); // true

By the way:

Mathf.Approximately(0.0f, 2.0f * float.Epsilon); // true
Mathf.Approximately(0.0f, 3.0f * float.Epsilon); // true
Mathf.Approximately(0.0f, 4.0f * float.Epsilon); // true
Mathf.Approximately(0.0f, 5.0f * float.Epsilon); // true
Mathf.Approximately(0.0f, 6.0f * float.Epsilon); // true
Mathf.Approximately(0.0f, 7.0f * float.Epsilon); // true

Mathf.Approximately(0.0f, 8.0f * float.Epsilon); // false
Mathf.Approximately(0.0f, 9.0f * float.Epsilon); // false

Q: Based on that evidence, can I safely say that Mathf.Approximately is not correctly implemented according to its documentation*?

(* and as a result, I should move to different solutions, such as the one in Floating point comparison functions for C#)

ianmandarini
  • 395
  • 1
  • 15
  • 1
    I can confirm that `Mathf.Epsilon == float.Epsilon`, so it does appear that the documentation is a bit... *loose* with describing its algorithm (An 'approximation' only ;). But do you really need the approximation to be that precise? – Immersive Oct 29 '19 at 03:18
  • 1
    I bumped into the question when I was implementing a function *public static bool IsInRangeOf(this float thisFloat, float otherFloat, float range)* that answers if *thisFloat* is in the interval (*otherFloat - range*, *otherFloat + range*). My naive implementation was *return Mathf.Abs(otherFloat - thisFloat) < range*, but it fails for the case *[TestCase(0.91f, 1.0f - 0.09f, float.Epsilon, ExpectedResult = true)]*. Eventually I bumped into the `Mathf.Approximately` function, that lead me to create the question here in Stack Overflow. – ianmandarini Oct 29 '19 at 04:14
  • So I think it is more of a matter of defining behavior of accepted test cases than **needing** the precision. e.g. I could define the constraint that the *range should be at least 10% of the difference between the two floats* and throw ArgumentOutOfRangeException if the input doesn't match that constraint. Or even, just accept that the function is subject to floating point errors, specially if the floats are very close to each other. All I wanted to get from here is if anyone has any information that perhaps I did not have about `Mathf.Approximately` so I can make an informed decision. – ianmandarini Oct 29 '19 at 04:21
  • @Immersive I think an answer that states that `Mathf.Epsilon == float.Epsilon`, that also states the documentation is a bit loose/the threshold is undocumented, and then asks the rhetoric question if the precision is really needed (perhaps adding that floating point operations errors are subject to error and one should always check against a error that heavily depends on the domain of values the floats can assume) is a good answer. Can you put that as an answer so we get this out of the comments and I can accept it? – ianmandarini Oct 29 '19 at 04:26
  • 1
    You are not the first one confused about this https://stackoverflow.com/questions/28471464/mathf-mathf-approximately-does-not-work – Menyus Oct 29 '19 at 09:43
  • As a point of order, Mathf.Epsilon does not always equal float.Epsilon. It equals float.Epsilon on platforms where float.Epsilon == 0 resolves to false, or something larger otherwise. [Source.](https://forum.unity.com/threads/difference-between-mathf-epsilon-and-float-epsilon.1132789/#post-7277812) Good question & accepted answer is helpful. – Ruzihm Nov 18 '21 at 17:44

2 Answers2

9

Here is the decompiled code of Unity's public static bool Mathf.Approximately(float a, float b); You can see the * 8.0f at the end ^^, so a truely badly documented method indeed.

/// <summary>
/// <para>Compares two floating point values if they are similar.</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static bool Approximately(float a, float b)
{
   return (double) Mathf.Abs(b - a) < (double) Mathf.Max(1E-06f * Mathf.Max(Mathf.Abs(a),
       Mathf.Abs(b)), Mathf.Epsilon * 8.0f);
}
Menyus
  • 6,633
  • 4
  • 16
  • 36
-1

It should be Mathf.Approximate(0.0f - 8.0f, float.Epsilon);. Since floats can have an insanely small floating number, you'll need to get subtract both float a and b to get that small number and compare it to float.epsilon.

[EDIT]

It was pointed out that Mathf.Approximate doesn't require an epsilon to be passed so Mathf.Approximate(float a, float b) should suffice. However, if you need to set your own threshold, you can use the above mentioned code.

  • Not really. `Mathf.Approximately()` is used to compare a *near-zero difference* between the two parameters using the (hidden/hard-coded) threshold of `Mathf.Epsilon`. It's not about 'how negative' the numbers are. – Immersive Oct 29 '19 at 02:50
  • There must be a problem with how I phrased it. I didn't mention that numbers must be negative or 'how negative' numbers should be. I was simply explaining that he should get the difference between the two floating variables in order to get a value that he will need to compare it to `Mathf.Epsilon`. – Kyle Kristopher Garcia Oct 29 '19 at 04:58
  • 1
    Ahh. Sorry. Yes. The approximation is a comparison of difference, but the signature for the function is `(compareLeft, compareRight)`, and the threshold for the comparison is hard-coded. So, there's no need to subtract a from b, just use a and b as the parameters. – Immersive Oct 29 '19 at 06:05
  • I just encountered Mathf.Approximately, I thought it needed an epsilon. I use my own approximate function that's why I thought that Mathf.Approximate needed an epsilon to be passed. I will correct my answer. – Kyle Kristopher Garcia Oct 29 '19 at 07:33