Continuing from the answers provided by Michael and testing, an important thing to keep in mind when translating the original Java code to C# is that Java and C# define their constants differently. C#, for instance, lacks Java's MIN_NORMAL, and the definitions for MinValue differ greatly.
Java defines MIN_VALUE to be the smallest possible positive value, while C# defines it as the smallest possible representable value overall. The equivalent value in C# is Epsilon.
The lack of MIN_NORMAL is problematic for direct translation of the original algorithm - without it, things start to break down for small values near zero. Java's MIN_NORMAL follows the IEEE specification of the smallest possible number without having the leading bit of the significand as zero, and with that in mind, we can define our own normals for both singles and doubles (which dbc mentioned in the comments to the original answer).
The following C# code for singles passes all of the tests given on The Floating Point Guide, and the double edition passes all of the tests with minor modifications in the test cases to account for the increased precision.
public static bool ApproximatelyEqualEpsilon(float a, float b, float epsilon)
{
const float floatNormal = (1 << 23) * float.Epsilon;
float absA = Math.Abs(a);
float absB = Math.Abs(b);
float diff = Math.Abs(a - b);
if (a == b)
{
// Shortcut, handles infinities
return true;
}
if (a == 0.0f || b == 0.0f || diff < floatNormal)
{
// a or b is zero, or both are extremely close to it.
// relative error is less meaningful here
return diff < (epsilon * floatNormal);
}
// use relative error
return diff / Math.Min((absA + absB), float.MaxValue) < epsilon;
}
The version for doubles is identical save for type changes and that the normal is defined like this instead.
const double doubleNormal = (1L << 52) * double.Epsilon;