Alex Herbert created NUMBERS-184:
------------------------------------

             Summary: Reduce number of operations in Precision.equals using a 
maxUlps
                 Key: NUMBERS-184
                 URL: https://issues.apache.org/jira/browse/NUMBERS-184
             Project: Commons Numbers
          Issue Type: Improvement
          Components: core
    Affects Versions: 1.0
            Reporter: Alex Herbert
             Fix For: 1.1


The Precision class has a method to test if two arguments are equal using a 
maximum number of representable float values between two arguments.

This is performed on the IEEE 754 bit layout of the floats. When the two inputs 
have opposite signs there is a lot of code to compute the distance of the 
values from the bit representation of +0.0 or -0.0. This is redundant. If the 
signs are opposite then the distance from the bit representation of 0.0 only 
requires dropping the sign bit from the bit representation. Here is an extract 
from the current method:
{code:java}
        final int xInt = Float.floatToRawIntBits(x);
        final int yInt = Float.floatToRawIntBits(y);

        final boolean isEqual;
        if (((xInt ^ yInt) & SGN_MASK_FLOAT) == 0) {
            // number have same sign, there is no risk of overflow
            isEqual = Math.abs(xInt - yInt) <= maxUlps;
        } else {
            // number have opposite signs, take care of overflow
            final int deltaPlus;
            final int deltaMinus;
            if (xInt < yInt) {
                deltaPlus  = yInt - POSITIVE_ZERO_FLOAT_BITS;
                deltaMinus = xInt - NEGATIVE_ZERO_FLOAT_BITS;
            } else {
                deltaPlus  = xInt - POSITIVE_ZERO_FLOAT_BITS;
                deltaMinus = yInt - NEGATIVE_ZERO_FLOAT_BITS;
            }            

            if (deltaPlus > maxUlps) {
                isEqual = false;
            } else {
                isEqual = deltaMinus <= (maxUlps - deltaPlus);
            }        
        }{code}
The second branch can be simplified using bit masking.
{code:java}
            final int deltaPlus = xInt & Integer.MAX_VALUE;
            final int deltaMinus = yInt & Integer.MAX_VALUE;   
            isEqual = (long) deltaPlus + deltaMinus <= maxUlps;{code}
For the float method overflow can be avoid by using a long to sum the two 
deltas eliminating a further branch condition.

An different optimisation can be performed for the double argument method. 
Since the ulp argument is an integer, when the signs are opposite then a NaN 
bit value would be at least (2047L << 52) above zero. Thus there is no need to 
check for NaN if the numbers are equal within the max ULPs and have opposite 
signs.

This optimisation could be made if using a short for the float equals method 
but would require breaking API changes and cannot be done. For reference the 
max difference for doubles is approximately 2^31 / 2^52 of the mantissa for 
double values with the same exponent. This is a relative error of approximately 
4.77e-7.

Using a short for floats would be 2^15 / 2^24 of the mantissa for a relative 
error of approximately 3.8e-3. Using an int argument allows an extreme relative 
error of 1 when both arguments are the same sign, and an absolute error of more 
than Float.MAX_VALUE. It makes no sense to compare two float values with a 
maximum possible ULP difference of more than the range from zero to infinity.

 



--
This message was sent by Atlassian Jira
(v8.20.1#820001)

Reply via email to