zhao
2021-07-02 081df17b8cc4a6e7e4f4e1e1887f24810e3ec2f9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using System;
using System.Collections.Generic;
using System.Text;
using HH.WMS.Utils.NPOI.Util;
 
namespace HH.WMS.Utils.NPOI.SS.Util
{
    public class NumberComparer
    {
        /**
         * This class attempts to reproduce Excel's behaviour for comparing numbers.  Results are
         * mostly the same as those from {@link Double#compare(double, double)} but with some
         * rounding.  For numbers that are very close, this code converts to a format having 15
         * decimal digits of precision and a decimal exponent, before completing the comparison.
         * <p/>
         * In Excel formula evaluation, expressions like "(0.06-0.01)=0.05" evaluate to "TRUE" even
         * though the equivalent java expression is <c>false</c>.  In examples like this,
         * Excel achieves the effect by having additional logic for comparison operations.
         * <p/>
         * <p/>
         * Note - Excel also gives special treatment to expressions like "0.06-0.01-0.05" which
         * evaluates to "0" (in java, rounding anomalies give a result of 6.9E-18).  The special
         * behaviour here is for different reasons to the example above:  If the last operator in a
         * cell formula is '+' or '-' and the result is less than 2<sup>50</sup> times smaller than
         * first operand, the result is rounded to zero.
         * Needless to say, the two rules are not consistent and it is relatively easy to find
         * examples that satisfy<br/>
         * "A=B" is "TRUE" but "A-B" is not "0"<br/>
         * and<br/>
         * "A=B" is "FALSE" but "A-B" is "0"<br/>
         * <br/>
         * This rule (for rounding the result of a final addition or subtraction), has not been
         * implemented in POI (as of Jul-2009).
         *
         * @return <code>negative, 0, or positive</code> according to the standard Excel comparison
         * of values <c>a</c> and <c>b</c>.
         */
        public static int Compare(double a, double b)
        {
            long rawBitsA = BitConverter.DoubleToInt64Bits(a);
            long rawBitsB = BitConverter.DoubleToInt64Bits(b);
 
            int biasedExponentA = IEEEDouble.GetBiasedExponent(rawBitsA);
            int biasedExponentB = IEEEDouble.GetBiasedExponent(rawBitsB);
 
            if (biasedExponentA == IEEEDouble.BIASED_EXPONENT_SPECIAL_VALUE)
            {
                throw new ArgumentException("Special double values are not allowed: " + ToHex(a));
            }
            if (biasedExponentB == IEEEDouble.BIASED_EXPONENT_SPECIAL_VALUE)
            {
                throw new ArgumentException("Special double values are not allowed: " + ToHex(a));
            }
 
            int cmp;
 
            // sign bit is in the same place for long and double:
            bool aIsNegative = rawBitsA < 0;
            bool bIsNegative = rawBitsB < 0;
 
            // compare signs
            if (aIsNegative != bIsNegative)
            {
                // Excel seems to have 'normal' comparison behaviour around zero (no rounding)
                // even -0.0 < +0.0 (which is not quite the initial conclusion of bug 47198)
                return aIsNegative ? -1 : +1;
            }
 
            // then compare magnitudes (IEEE 754 has exponent bias specifically to allow this)
            cmp = biasedExponentA - biasedExponentB;
            int absExpDiff = Math.Abs(cmp);
            if (absExpDiff > 1)
            {
                return aIsNegative ? -cmp : cmp;
            }
 
            if (absExpDiff == 1)
            {
                // special case exponent differs by 1.  There is still a chance that with rounding the two quantities could end up the same
 
            }
            else
            {
                // else - sign and exponents equal
                if (rawBitsA == rawBitsB)
                {
                    // fully equal - exit here
                    return 0;
                }
            }
            if (biasedExponentA == 0)
            {
                if (biasedExponentB == 0)
                {
                    return CompareSubnormalNumbers(rawBitsA & IEEEDouble.FRAC_MASK, rawBitsB & IEEEDouble.FRAC_MASK, aIsNegative);
                }
                // else biasedExponentB is 1
                return -CompareAcrossSubnormalThreshold(rawBitsB, rawBitsA, aIsNegative);
            }
            if (biasedExponentB == 0)
            {
                // else biasedExponentA is 1
                return +CompareAcrossSubnormalThreshold(rawBitsA, rawBitsB, aIsNegative);
            }
 
            // sign and exponents same, but fractional bits are different
 
            ExpandedDouble edA = ExpandedDouble.FromRawBitsAndExponent(rawBitsA, biasedExponentA - IEEEDouble.EXPONENT_BIAS);
            ExpandedDouble edB = ExpandedDouble.FromRawBitsAndExponent(rawBitsB, biasedExponentB - IEEEDouble.EXPONENT_BIAS);
            NormalisedDecimal ndA = edA.NormaliseBaseTen().RoundUnits();
            NormalisedDecimal ndB = edB.NormaliseBaseTen().RoundUnits();
            cmp = ndA.CompareNormalised(ndB);
            if (aIsNegative)
            {
                return -cmp;
            }
            return cmp;
        }
 
        /**
         * If both numbers are subnormal, Excel seems to use standard comparison rules
         */
        private static int CompareSubnormalNumbers(long fracA, long fracB, bool isNegative)
        {
            int cmp = fracA > fracB ? +1 : fracA < fracB ? -1 : 0;
 
            return isNegative ? -cmp : cmp;
        }
 
 
 
        /**
         * Usually any normal number is greater (in magnitude) than any subnormal number.
         * However there are some anomalous cases around the threshold where Excel produces screwy results
         * @param isNegative both values are either negative or positive. This parameter affects the sign of the comparison result
         * @return usually <code>isNegative ? -1 : +1</code>
         */
        private static int CompareAcrossSubnormalThreshold(long normalRawBitsA, long subnormalRawBitsB, bool isNegative)
        {
            long fracB = subnormalRawBitsB & IEEEDouble.FRAC_MASK;
            if (fracB == 0)
            {
                // B is zero, so A is definitely greater than B
                return isNegative ? -1 : +1;
            }
            long fracA = normalRawBitsA & IEEEDouble.FRAC_MASK;
            if (fracA <= 0x0000000000000007L && fracB >= 0x000FFFFFFFFFFFFAL)
            {
                // Both A and B close to threshold - weird results
                if (fracA == 0x0000000000000007L && fracB == 0x000FFFFFFFFFFFFAL)
                {
                    // special case
                    return 0;
                }
                // exactly the opposite
                return isNegative ? +1 : -1;
            }
            // else - typical case A and B is not close to threshold
            return isNegative ? -1 : +1;
        }
 
 
 
        /**
         * for formatting double values in error messages
         */
        private static String ToHex(double a)
        {
            return "0x" + StringUtil.ToHexString(BitConverter.DoubleToInt64Bits(a)).ToUpper();
        }
    }
}