Is Mathf.Approximately(0.0f, float.Epsilon) == true its correct behavior?
Asked Answered
O

2

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#)

Omnivore answered 28/10, 2019 at 22:57 Comment(6)
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?Envisage
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.Omnivore
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.Omnivore
@Envisage 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?Omnivore
You are not the first one confused about this #28471964Fenwick
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. Good question & accepted answer is helpful.Stanislaus
F
10

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);
}
Fenwick answered 29/10, 2019 at 9:46 Comment(0)
M
-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.

Munn answered 29/10, 2019 at 0:17 Comment(4)
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.Envisage
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.Munn
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.Envisage
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.Munn

© 2022 - 2024 — McMap. All rights reserved.