@@ -55,15 +55,47 @@ object MathUtil {
5555 if (a == b) 0L
5656 else if (java.lang.Double .isNaN(a) || java.lang.Double .isNaN(b)) Long .MaxValue
5757 else if (a.isInfinite || b.isInfinite) Long .MaxValue
58- else if ((a < 0 ) != (b < 0 )) Long .MaxValue
59- else Math .abs(java.lang.Double .doubleToLongBits(a) - java.lang.Double .doubleToLongBits(b))
58+ else {
59+ val aBits = java.lang.Double .doubleToLongBits(a)
60+ val bBits = java.lang.Double .doubleToLongBits(b)
61+ // The sign of the *representation* is compared rather than the sign of the value: -0.0 is not `< 0`, but its
62+ // representation is Long.MinValue, so treating it as non-negative would overflow the subtraction below.
63+ // Representations that share a sign bit lie in the same half of the long range, so the subtraction is safe.
64+ if ((aBits < 0 ) != (bBits < 0 )) Long .MaxValue else Math .abs(aBits - bBits)
65+ }
6066 }
6167
6268 /** True if `a` and `b` are within either an absolute `epsilon` or `maxUlps` ulps of one another. */
6369 private def approximatelyEqual (a : Double , b : Double , epsilon : Double , maxUlps : Int ): Boolean = {
6470 Math .abs(a - b) <= epsilon || ulpsBetween(a, b) <= maxUlps
6571 }
6672
73+ /** True if more than one eligible entry of `ds` is approximately equal to `target`.
74+ *
75+ * `NaN`s are always ineligible, and `-Infinity` is ineligible unless `allowNegativeInfinity` is set, mirroring
76+ * the entries that were candidates for the extremum in the first place. The scan stops as soon as a second
77+ * match is seen; these functions sit on the consensus calling hot path, so neither the predicate nor the count
78+ * is worth materialising.
79+ */
80+ private def hasApproximateTie (ds : Array [Double ],
81+ target : Double ,
82+ epsilon : Double ,
83+ maxUlps : Int ,
84+ allowNegativeInfinity : Boolean ): Boolean = {
85+ var seen = false
86+ var idx = 0
87+ while (idx < ds.length) {
88+ val v = ds(idx)
89+ if (! java.lang.Double .isNaN(v) && (Double .NegativeInfinity != v || allowNegativeInfinity) &&
90+ approximatelyEqual(v, target, epsilon, maxUlps)) {
91+ if (seen) return true
92+ seen = true
93+ }
94+ idx += 1
95+ }
96+ false
97+ }
98+
6799 /** Calculates the arithmetic mean of an array of bytes. The computation is performed
68100 * in integer space and the result will therefore always round down.
69101 *
@@ -104,18 +136,15 @@ object MathUtil {
104136 */
105137 def minWithIndex (ds : Array [Double ], allowNegativeInfinity : Boolean = false , requireUniqueMinimum : Boolean = false , epsilon : Double = MathUtil .epsilon, maxUlps : Int = MathUtil .maxUlps): (Double , Int ) = {
106138 if (ds.length == 0 ) throw new NoSuchElementException (" Cannot find the min of a zero length array." )
107- def eligible (v : Double ): Boolean = {
108- ! java.lang.Double .isNaN(v) && (Double .NegativeInfinity != v || allowNegativeInfinity)
109- }
110-
111139 var min = Double .MaxValue
112140 var minIndex = - 1
113141 var assigned = false
114142 val len = ds.length
115143 var idx = 0
116144 while (idx < len) {
117145 val v = ds(idx)
118- if (eligible(v) && (! assigned || v < min)) {
146+ if (! java.lang.Double .isNaN(v) && (Double .NegativeInfinity != v || allowNegativeInfinity) &&
147+ (! assigned || v < min)) {
119148 min = v
120149 minIndex = idx
121150 assigned = true
@@ -126,28 +155,12 @@ object MathUtil {
126155
127156 if (! assigned) throw new NoSuchElementException (" All values are NaNs or negative infinity." )
128157
129- // Ties are counted against the final minimum, not a running one: a near-tie is a property of the values, and
158+ // Ties are detected against the final minimum, not a running one: a near-tie is a property of the values, and
130159 // comparing against a running minimum would only detect it when the tied value happens to appear later.
131- if (requireUniqueMinimum && countApproximatelyEqual (ds, min, epsilon, maxUlps, eligible) > 1 ) (min, - 1 )
160+ if (requireUniqueMinimum && hasApproximateTie (ds, min, epsilon, maxUlps, allowNegativeInfinity) ) (min, - 1 )
132161 else (min, minIndex)
133162 }
134163
135- /** Counts the eligible entries of `ds` that are approximately equal to `target`. */
136- private def countApproximatelyEqual (ds : Array [Double ],
137- target : Double ,
138- epsilon : Double ,
139- maxUlps : Int ,
140- eligible : Double => Boolean ): Int = {
141- var count = 0
142- var idx = 0
143- while (idx < ds.length) {
144- val v = ds(idx)
145- if (eligible(v) && approximatelyEqual(v, target, epsilon, maxUlps)) count += 1
146- idx += 1
147- }
148- count
149- }
150-
151164
152165 /**
153166 * Finds the maximum element in the array and returns it with its index.
@@ -167,16 +180,14 @@ object MathUtil {
167180 */
168181 def maxWithIndex (ds : Array [Double ], requireUniqueMaximum : Boolean = false , epsilon : Double = MathUtil .epsilon, maxUlps : Int = MathUtil .maxUlps): (Double ,Int ) = {
169182 if (ds.length == 0 ) throw new NoSuchElementException (" Cannot find the max of a zero length array." )
170- def eligible (v : Double ): Boolean = ! java.lang.Double .isNaN(v)
171-
172183 var max = Double .MinValue
173184 var maxIndex = - 1
174185 var assigned = false
175186 val len = ds.length
176187 var idx = 0
177188 while (idx < len) {
178189 val v = ds(idx)
179- if (eligible (v) && (! assigned || v > max)) {
190+ if (! java.lang. Double .isNaN (v) && (! assigned || v > max)) {
180191 max = v
181192 maxIndex = idx
182193 assigned = true
@@ -186,9 +197,10 @@ object MathUtil {
186197
187198 if (! assigned) throw new NoSuchElementException (" Array contained only NaNs." )
188199
189- // Ties are counted against the final maximum, not a running one: a near-tie is a property of the values, and
200+ // Ties are detected against the final maximum, not a running one: a near-tie is a property of the values, and
190201 // comparing against a running maximum would only detect it when the tied value happens to appear later.
191- if (requireUniqueMaximum && countApproximatelyEqual(ds, max, epsilon, maxUlps, eligible) > 1 ) (max, - 1 )
202+ // Negative infinities are eligible here: unlike the minimum, they are never excluded from the maximum.
203+ if (requireUniqueMaximum && hasApproximateTie(ds, max, epsilon, maxUlps, allowNegativeInfinity= true )) (max, - 1 )
192204 else (max, maxIndex)
193205 }
194206}
0 commit comments