Skip to content

Commit 0a05589

Browse files
committed
bug#10511 Add total orderings for Float and Double
Add total orderings for Float and Double, so that there are two implicit orderings for each in scope: one consistent with a total ordering, and one consistent with IEEE spec.
1 parent 7d6fc36 commit 0a05589

File tree

8 files changed

+240
-44
lines changed

8 files changed

+240
-44
lines changed

src/library/scala/collection/immutable/NumericRange.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,8 @@ object NumericRange {
390390
Numeric.ByteIsIntegral -> Ordering.Byte,
391391
Numeric.CharIsIntegral -> Ordering.Char,
392392
Numeric.LongIsIntegral -> Ordering.Long,
393-
Numeric.FloatAsIfIntegral -> Ordering.Float,
394-
Numeric.DoubleAsIfIntegral -> Ordering.Double,
393+
Numeric.FloatAsIfIntegral -> Ordering.FloatIeeeOrdering,
394+
Numeric.DoubleAsIfIntegral -> Ordering.DoubleIeeeOrdering,
395395
Numeric.BigDecimalAsIfIntegral -> Ordering.BigDecimal
396396
)
397397

src/library/scala/math/Numeric.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,8 @@ object Numeric {
147147
def quot(x: Float, y: Float): Float = (BigDecimal(x) quot BigDecimal(y)).floatValue
148148
def rem(x: Float, y: Float): Float = (BigDecimal(x) remainder BigDecimal(y)).floatValue
149149
}
150-
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering
151-
object FloatAsIfIntegral extends FloatAsIfIntegral with Ordering.FloatOrdering {
152-
}
150+
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatIeeeOrdering
151+
object FloatAsIfIntegral extends FloatAsIfIntegral with Ordering.FloatIeeeOrdering
153152

154153
trait DoubleIsConflicted extends Numeric[Double] {
155154
def plus(x: Double, y: Double): Double = x + y
@@ -199,8 +198,8 @@ object Numeric {
199198
implicit object BigDecimalIsFractional extends BigDecimalIsFractional with Ordering.BigDecimalOrdering
200199
object BigDecimalAsIfIntegral extends BigDecimalAsIfIntegral with Ordering.BigDecimalOrdering
201200

202-
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering
203-
object DoubleAsIfIntegral extends DoubleAsIfIntegral with Ordering.DoubleOrdering
201+
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleIeeeOrdering
202+
object DoubleAsIfIntegral extends DoubleAsIfIntegral with Ordering.DoubleIeeeOrdering
204203
}
205204

206205
trait Numeric[T] extends Ordering[T] {

src/library/scala/math/Ordering.scala

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,21 @@ trait LowPriorityOrderingImplicits {
212212
*
213213
* It contains many implicit orderings as well as well as methods to construct
214214
* new orderings.
215+
*
216+
* @define doubleOrdering Because the behaviour of `Double`s specified by IEEE is
217+
* not consistent with a total ordering when dealing with
218+
* `NaN`, there are two orderings defined for `Double`:
219+
* `DoubleTotalOrdering`, which is consistent with a total
220+
* ordering, and `DoubleIeeeOrdering`, which is consistent
221+
* as much as possible with IEEE spec and floating point
222+
* operations defined in [[scala.math]].
223+
* @define floatOrdering Because the behaviour of `Float`s specified by IEEE is
224+
* not consistent with a total ordering when dealing with
225+
* `NaN`, there are two orderings defined for `Float`:
226+
* `FloatTotalOrdering`, which is consistent with a total
227+
* ordering, and `FloatIeeeOrdering`, which is consistent
228+
* as much as possible with IEEE spec and floating point
229+
* operations defined in [[scala.math]].
215230
*/
216231
object Ordering extends LowPriorityOrderingImplicits {
217232
@inline def apply[T](implicit ord: Ordering[T]) = ord
@@ -312,9 +327,38 @@ object Ordering extends LowPriorityOrderingImplicits {
312327
}
313328
implicit object Long extends LongOrdering
314329

315-
trait FloatOrdering extends Ordering[Float] {
316-
outer =>
330+
/** An ordering for `Float`s which is a fully consistent total ordering,
331+
* and treats `NaN` as larger than all other `Float` values; it behaves
332+
* the same as [[java.lang.Float.compare()]].
333+
*
334+
* $floatOrdering
335+
*
336+
* This ordering may be preferable for sorting collections.
337+
*
338+
* @see [[FloatIeeeOrdering]]
339+
*/
340+
trait FloatTotalOrdering extends Ordering[Float] {
341+
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
342+
}
343+
implicit object FloatTotalOrdering extends FloatTotalOrdering
317344

345+
/** An ordering for `Float`s which is consistent with IEEE specifications
346+
* whenever possible.
347+
*
348+
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
349+
* comparison operations for `Float`s, and return `false` when called with
350+
* `NaN`.
351+
* - `min` and `max` are consistent with `math.min` and `math.max`, and
352+
* return `NaN` when called with `NaN` as either argument.
353+
* - `compare` behaves the same as [[java.lang.Float.compare()]].
354+
*
355+
* $floatOrdering
356+
*
357+
* This ordering may be preferable for numeric contexts.
358+
*
359+
* @see [[FloatTotalOrdering]]
360+
*/
361+
trait FloatIeeeOrdering extends Ordering[Float] {
318362
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
319363

320364
override def lteq(x: Float, y: Float): Boolean = x <= y
@@ -324,25 +368,41 @@ object Ordering extends LowPriorityOrderingImplicits {
324368
override def equiv(x: Float, y: Float): Boolean = x == y
325369
override def max(x: Float, y: Float): Float = math.max(x, y)
326370
override def min(x: Float, y: Float): Float = math.min(x, y)
327-
328-
override def reverse: Ordering[Float] = new FloatOrdering {
329-
override def reverse = outer
330-
override def compare(x: Float, y: Float) = outer.compare(y, x)
331-
332-
override def lteq(x: Float, y: Float): Boolean = outer.lteq(y, x)
333-
override def gteq(x: Float, y: Float): Boolean = outer.gteq(y, x)
334-
override def lt(x: Float, y: Float): Boolean = outer.lt(y, x)
335-
override def gt(x: Float, y: Float): Boolean = outer.gt(y, x)
336-
override def min(x: Float, y: Float): Float = outer.max(x, y)
337-
override def max(x: Float, y: Float): Float = outer.min(x, y)
338-
339-
}
340371
}
341-
implicit object Float extends FloatOrdering
372+
implicit object FloatIeeeOrdering extends FloatIeeeOrdering
342373

343-
trait DoubleOrdering extends Ordering[Double] {
344-
outer =>
374+
/** An ordering for `Double`s which is a fully consistent total ordering,
375+
* and treats `NaN` as larger than all other `Double` values; it behaves
376+
* the same as [[java.lang.Double.compare()]].
377+
*
378+
* $doubleOrdering
379+
*
380+
* This ordering may be preferable for sorting collections.
381+
*
382+
* @see [[DoubleIeeeOrdering]]
383+
*/
384+
trait DoubleTotalOrdering extends Ordering[Double] {
385+
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
386+
}
387+
implicit object DoubleTotalOrdering extends DoubleTotalOrdering
345388

389+
/** An ordering for `Double`s which is consistent with IEEE specifications
390+
* whenever possible.
391+
*
392+
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
393+
* comparison operations for `Double`s, and return `false` when called with
394+
* `NaN`.
395+
* - `min` and `max` are consistent with `math.min` and `math.max`, and
396+
* return `NaN` when called with `NaN` as either argument.
397+
* - `compare` behaves the same as [[java.lang.Double.compare()]].
398+
*
399+
* $doubleOrdering
400+
*
401+
* This ordering may be preferable for numeric contexts.
402+
*
403+
* @see [[DoubleTotalOrdering]]
404+
*/
405+
trait DoubleIeeeOrdering extends Ordering[Double] {
346406
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
347407

348408
override def lteq(x: Double, y: Double): Boolean = x <= y
@@ -352,20 +412,9 @@ object Ordering extends LowPriorityOrderingImplicits {
352412
override def equiv(x: Double, y: Double): Boolean = x == y
353413
override def max(x: Double, y: Double): Double = math.max(x, y)
354414
override def min(x: Double, y: Double): Double = math.min(x, y)
355-
356-
override def reverse: Ordering[Double] = new DoubleOrdering {
357-
override def reverse = outer
358-
override def compare(x: Double, y: Double) = outer.compare(y, x)
359-
360-
override def lteq(x: Double, y: Double): Boolean = outer.lteq(y, x)
361-
override def gteq(x: Double, y: Double): Boolean = outer.gteq(y, x)
362-
override def lt(x: Double, y: Double): Boolean = outer.lt(y, x)
363-
override def gt(x: Double, y: Double): Boolean = outer.gt(y, x)
364-
override def min(x: Double, y: Double): Double = outer.max(x, y)
365-
override def max(x: Double, y: Double): Double = outer.min(x, y)
366-
}
367415
}
368-
implicit object Double extends DoubleOrdering
416+
417+
implicit object DoubleIeeeOrdering extends DoubleIeeeOrdering
369418

370419
trait BigIntOrdering extends Ordering[BigInt] {
371420
def compare(x: BigInt, y: BigInt) = x.compare(y)

src/library/scala/runtime/RichDouble.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ package scala
1010
package runtime
1111

1212
final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Double] {
13-
protected def num = scala.math.Numeric.DoubleIsFractional
14-
protected def ord = scala.math.Ordering.Double
13+
protected def num = scala.math.Numeric.DoubleIsFractional
14+
protected def ord = scala.math.Ordering.DoubleTotalOrdering
1515
protected def integralNum = scala.math.Numeric.DoubleAsIfIntegral
1616

1717
override def doubleValue() = self

src/library/scala/runtime/RichFloat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package runtime
1111

1212
final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float] {
1313
protected def num = scala.math.Numeric.FloatIsFractional
14-
protected def ord = scala.math.Ordering.Float
14+
protected def ord = scala.math.Ordering.FloatTotalOrdering
1515
protected def integralNum = scala.math.Numeric.FloatAsIfIntegral
1616

1717
override def doubleValue() = self.toDouble

test/files/neg/t10511.check

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
t10511.scala:2: error: ambiguous implicit values:
2+
both object FloatTotalOrdering in object Ordering of type scala.math.Ordering.FloatTotalOrdering.type
3+
and object FloatIeeeOrdering in object Ordering of type scala.math.Ordering.FloatIeeeOrdering.type
4+
match expected type scala.math.Ordering[Float]
5+
val f = Ordering[Float]
6+
^
7+
t10511.scala:3: error: ambiguous implicit values:
8+
both object DoubleTotalOrdering in object Ordering of type scala.math.Ordering.DoubleTotalOrdering.type
9+
and object DoubleIeeeOrdering in object Ordering of type scala.math.Ordering.DoubleIeeeOrdering.type
10+
match expected type scala.math.Ordering[Double]
11+
val d = Ordering[Double]
12+
^
13+
t10511.scala:6: error: ambiguous implicit values:
14+
both object DoubleTotalOrdering in object Ordering of type scala.math.Ordering.DoubleTotalOrdering.type
15+
and object DoubleIeeeOrdering in object Ordering of type scala.math.Ordering.DoubleIeeeOrdering.type
16+
match expected type scala.math.Ordering[Double]
17+
list.sorted
18+
^
19+
three errors found

test/files/neg/t10511.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test {
2+
val f = Ordering[Float]
3+
val d = Ordering[Double]
4+
5+
val list = List(1.0, 2.0, 3.0)
6+
list.sorted
7+
}

test/junit/scala/math/OrderingTest.scala

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,38 @@ import org.junit.Test
55
import org.junit.runner.RunWith
66
import org.junit.runners.JUnit4
77

8+
import scala.math.Ordering.{FloatTotalOrdering, DoubleTotalOrdering}
9+
10+
import java.{lang => jl}
11+
812
@RunWith(classOf[JUnit4])
913
class OrderingTest {
14+
val floats = Seq(
15+
Float.NegativeInfinity,
16+
Float.MinValue,
17+
-1f,
18+
-0f,
19+
0f,
20+
Float.MinPositiveValue,
21+
1f,
22+
Float.MaxValue,
23+
Float.PositiveInfinity,
24+
Float.NaN
25+
)
26+
27+
val doubles = Seq(
28+
Double.NegativeInfinity,
29+
Double.MinValue,
30+
-1d,
31+
-0d,
32+
0d,
33+
Double.MinPositiveValue,
34+
1d,
35+
Double.MaxValue,
36+
Double.PositiveInfinity,
37+
Double.NaN
38+
)
39+
1040

1141
/* Test for scala/bug#9077 */
1242
@Test
@@ -46,8 +76,8 @@ class OrderingTest {
4676
checkAll[Char](Char.MinValue, -1.toChar, 0.toChar, 1.toChar, Char.MaxValue)
4777
checkAll[Short](Short.MinValue, -1, 0, 1, Short.MaxValue)
4878
checkAll[Int](Int.MinValue, -1, 0, 1, Int.MaxValue)
49-
checkAll[Double](Double.MinValue, -1, -0, 0, 1, Double.MaxValue)
50-
checkAll[Float](Float.MinValue, -1, -0, 0, 1, Float.MaxValue)
79+
checkAll[Double](doubles: _*)
80+
checkAll[Float](floats: _*)
5181

5282
checkAll[BigInt](Int.MinValue, -1, 0, 1, Int.MaxValue)
5383
checkAll[BigDecimal](Int.MinValue, -1, -0, 1, Int.MaxValue)
@@ -76,5 +106,97 @@ class OrderingTest {
76106
check(o1, o2)
77107
check(o1, o3)
78108
}
79-
}
80109

110+
/* Test for scala/bug#10511 */
111+
@Test
112+
def testFloatDoubleTotalOrdering(): Unit = {
113+
val fNegZeroBits = jl.Float.floatToRawIntBits(-0.0f)
114+
val fPosZeroBits = jl.Float.floatToRawIntBits(0.0f)
115+
116+
val dNegZeroBits = jl.Double.doubleToRawLongBits(-0.0d)
117+
val dPosZeroBits = jl.Double.doubleToRawLongBits(0.0d)
118+
119+
def checkFloats(floats: Float*): Unit = {
120+
def same(f1: Float, f2: Float): Boolean = {
121+
val thisBits = jl.Float.floatToRawIntBits(f1)
122+
if (thisBits == fNegZeroBits) jl.Float.floatToRawIntBits(f2) == fNegZeroBits
123+
else if (thisBits == fPosZeroBits) jl.Float.floatToRawIntBits(f2) == fPosZeroBits
124+
else f1 == f2 || (jl.Float.isNaN(f1) && jl.Float.isNaN(f2))
125+
}
126+
127+
val O = Ordering[Float]
128+
for (i <- floats; j <- floats) {
129+
val msg = s"for i=$i, j=$j"
130+
131+
// consistency with `compare`
132+
assertEquals(msg, O.compare(i, j) < 0, O.lt(i, j))
133+
assertEquals(msg, O.compare(i, j) <= 0, O.lteq(i, j))
134+
assertEquals(msg, O.compare(i, j) == 0, O.equiv(i, j))
135+
assertEquals(msg, O.compare(i, j) >= 0, O.gteq(i, j))
136+
assertEquals(msg, O.compare(i, j) > 0, O.gt(i, j))
137+
138+
// consistency with other ops
139+
assertTrue(msg, O.lteq(i, j) || O.gteq(i, j))
140+
assertTrue(msg, O.lteq(i, j) || O.gt(i, j))
141+
assertTrue(msg, O.lteq(i, j) != O.gt(i, j))
142+
assertTrue(msg, O.lt(i, j) || O.gteq(i, j))
143+
assertTrue(msg, O.lt(i, j) != O.gteq(i, j))
144+
// exactly one of `lt`, `equiv`, `gt` is true
145+
assertTrue(msg,
146+
(O.lt(i, j) ^ O.equiv(i, j) ^ O.gt(i, j))
147+
&& !(O.lt(i, j) && O.equiv(i, j) && O.gt(i, j)))
148+
149+
// consistency with `max` and `min`
150+
assertEquals(msg, O.compare(i, j) >= 0, same(O.max(i, j), i))
151+
assertEquals(msg, O.compare(i, j) <= 0, same(O.min(i, j), i))
152+
if (!same(i, j)) {
153+
assertEquals(msg, O.compare(i, j) < 0, same(O.max(i, j), j))
154+
assertEquals(msg, O.compare(i, j) > 0, same(O.min(i, j), j))
155+
}
156+
}
157+
}
158+
159+
def checkDoubles(doubles: Double*): Unit = {
160+
def same(d1: Double, d2: Double): Boolean = {
161+
val thisBits = jl.Double.doubleToRawLongBits(d1)
162+
if (thisBits == dNegZeroBits) jl.Double.doubleToRawLongBits(d2) == dNegZeroBits
163+
else if (thisBits == dPosZeroBits) jl.Double.doubleToRawLongBits(d2) == dPosZeroBits
164+
else d1 == d2 || (jl.Double.isNaN(d1) && jl.Double.isNaN(d2))
165+
}
166+
167+
val O = Ordering[Double]
168+
for (i <- doubles; j <- doubles) {
169+
val msg = s"for i=$i, j=$j"
170+
171+
// consistency with `compare`
172+
assertEquals(msg, O.compare(i, j) < 0, O.lt(i, j))
173+
assertEquals(msg, O.compare(i, j) <= 0, O.lteq(i, j))
174+
assertEquals(msg, O.compare(i, j) == 0, O.equiv(i, j))
175+
assertEquals(msg, O.compare(i, j) >= 0, O.gteq(i, j))
176+
assertEquals(msg, O.compare(i, j) > 0, O.gt(i, j))
177+
178+
// consistency with other ops
179+
assertTrue(msg, O.lteq(i, j) || O.gteq(i, j))
180+
assertTrue(msg, O.lteq(i, j) || O.gt(i, j))
181+
assertTrue(msg, O.lteq(i, j) != O.gt(i, j))
182+
assertTrue(msg, O.lt(i, j) || O.gteq(i, j))
183+
assertTrue(msg, O.lt(i, j) != O.gteq(i, j))
184+
// exactly one of `lt`, `equiv`, `gt` is true
185+
assertTrue(msg,
186+
(O.lt(i, j) ^ O.equiv(i, j) ^ O.gt(i, j))
187+
&& !(O.lt(i, j) && O.equiv(i, j) && O.gt(i, j)))
188+
189+
// consistency with `max` and `min`
190+
assertEquals(msg, O.compare(i, j) >= 0, same(O.max(i, j), i))
191+
assertEquals(msg, O.compare(i, j) <= 0, same(O.min(i, j), i))
192+
if (!same(i, j)) {
193+
assertEquals(msg, O.compare(i, j) < 0, same(O.max(i, j), j))
194+
assertEquals(msg, O.compare(i, j) > 0, same(O.min(i, j), j))
195+
}
196+
}
197+
}
198+
199+
checkFloats(floats: _*)
200+
checkDoubles(doubles: _*)
201+
}
202+
}

0 commit comments

Comments
 (0)