Skip to content

Commit b6982c8

Browse files
authored
Merge pull request scala#6410 from NthPortal/bug#10511/R6
bug#10511 Add total orderings for Float and Double
2 parents e4ba457 + b686173 commit b6982c8

File tree

10 files changed

+269
-31
lines changed

10 files changed

+269
-31
lines changed

src/library/scala/math/Numeric.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ object Numeric {
141141
// logic in Numeric base trait mishandles abs(-0.0f)
142142
override def abs(x: Float): Float = math.abs(x)
143143
}
144-
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering
144+
implicit object FloatIsFractional extends FloatIsFractional with Ordering.Float.IeeeOrdering
145145

146146
trait DoubleIsFractional extends Fractional[Double] {
147147
def plus(x: Double, y: Double): Double = x + y
@@ -158,7 +158,7 @@ object Numeric {
158158
// logic in Numeric base trait mishandles abs(-0.0)
159159
override def abs(x: Double): Double = math.abs(x)
160160
}
161-
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering
161+
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.Double.IeeeOrdering
162162

163163
trait BigDecimalIsConflicted extends Numeric[BigDecimal] {
164164
def plus(x: BigDecimal, y: BigDecimal): BigDecimal = x + y

src/library/scala/math/Ordering.scala

Lines changed: 119 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ package scala
1010
package math
1111

1212
import java.util.Comparator
13-
import scala.language.{implicitConversions, higherKinds}
13+
14+
import scala.annotation.implicitAmbiguous
15+
import scala.language.{higherKinds, implicitConversions}
1416

1517
/** Ordering is a trait whose instances each represent a strategy for sorting
1618
* instances of a type.
@@ -311,31 +313,125 @@ object Ordering extends LowPriorityOrderingImplicits {
311313
}
312314
implicit object Long extends LongOrdering
313315

314-
trait FloatOrdering extends Ordering[Float] {
315-
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
316+
/** `Ordering`s for `Float`s.
317+
*
318+
* @define floatOrdering Because the behaviour of `Float`s specified by IEEE is
319+
* not consistent with a total ordering when dealing with
320+
* `NaN`, there are two orderings defined for `Float`:
321+
* `TotalOrdering`, which is consistent with a total
322+
* ordering, and `IeeeOrdering`, which is consistent
323+
* as much as possible with IEEE spec and floating point
324+
* operations defined in [[scala.math]].
325+
*/
326+
object Float {
327+
/** An ordering for `Float`s which is a fully consistent total ordering,
328+
* and treats `NaN` as larger than all other `Float` values; it behaves
329+
* the same as [[java.lang.Float.compare()]].
330+
*
331+
* $floatOrdering
332+
*
333+
* This ordering may be preferable for sorting collections.
334+
*
335+
* @see [[IeeeOrdering]]
336+
*/
337+
trait TotalOrdering extends Ordering[Float] {
338+
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
339+
}
340+
implicit object TotalOrdering extends TotalOrdering
316341

317-
override def lteq(x: Float, y: Float): Boolean = x <= y
318-
override def gteq(x: Float, y: Float): Boolean = x >= y
319-
override def lt(x: Float, y: Float): Boolean = x < y
320-
override def gt(x: Float, y: Float): Boolean = x > y
321-
override def equiv(x: Float, y: Float): Boolean = x == y
322-
override def max(x: Float, y: Float): Float = math.max(x, y)
323-
override def min(x: Float, y: Float): Float = math.min(x, y)
342+
/** An ordering for `Float`s which is consistent with IEEE specifications
343+
* whenever possible.
344+
*
345+
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
346+
* comparison operations for `Float`s, and return `false` when called with
347+
* `NaN`.
348+
* - `min` and `max` are consistent with `math.min` and `math.max`, and
349+
* return `NaN` when called with `NaN` as either argument.
350+
* - `compare` behaves the same as [[java.lang.Float.compare()]].
351+
*
352+
* $floatOrdering
353+
*
354+
* This ordering may be preferable for numeric contexts.
355+
*
356+
* @see [[TotalOrdering]]
357+
*/
358+
trait IeeeOrdering extends Ordering[Float] {
359+
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
360+
361+
override def lteq(x: Float, y: Float): Boolean = x <= y
362+
override def gteq(x: Float, y: Float): Boolean = x >= y
363+
override def lt(x: Float, y: Float): Boolean = x < y
364+
override def gt(x: Float, y: Float): Boolean = x > y
365+
override def equiv(x: Float, y: Float): Boolean = x == y
366+
override def max(x: Float, y: Float): Float = math.max(x, y)
367+
override def min(x: Float, y: Float): Float = math.min(x, y)
368+
}
369+
implicit object IeeeOrdering extends IeeeOrdering
324370
}
325-
implicit object Float extends FloatOrdering
326-
327-
trait DoubleOrdering extends Ordering[Double] {
328-
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
329-
330-
override def lteq(x: Double, y: Double): Boolean = x <= y
331-
override def gteq(x: Double, y: Double): Boolean = x >= y
332-
override def lt(x: Double, y: Double): Boolean = x < y
333-
override def gt(x: Double, y: Double): Boolean = x > y
334-
override def equiv(x: Double, y: Double): Boolean = x == y
335-
override def max(x: Double, y: Double): Double = math.max(x, y)
336-
override def min(x: Double, y: Double): Double = math.min(x, y)
371+
@deprecated("There are multiple ways to order Floats (Ordering.Float.TotalOrdering, " +
372+
"Ordering.Float.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it " +
373+
"explicitly. See the documentation for details.", since = "2.13.0")
374+
implicit object DeprecatedFloatOrdering extends Float.TotalOrdering
375+
376+
/** `Ordering`s for `Double`s.
377+
*
378+
* @define doubleOrdering Because the behaviour of `Double`s specified by IEEE is
379+
* not consistent with a total ordering when dealing with
380+
* `NaN`, there are two orderings defined for `Double`:
381+
* `TotalOrdering`, which is consistent with a total
382+
* ordering, and `IeeeOrdering`, which is consistent
383+
* as much as possible with IEEE spec and floating point
384+
* operations defined in [[scala.math]].
385+
*/
386+
object Double {
387+
/** An ordering for `Double`s which is a fully consistent total ordering,
388+
* and treats `NaN` as larger than all other `Double` values; it behaves
389+
* the same as [[java.lang.Double.compare()]].
390+
*
391+
* $doubleOrdering
392+
*
393+
* This ordering may be preferable for sorting collections.
394+
*
395+
* @see [[IeeeOrdering]]
396+
*/
397+
trait TotalOrdering extends Ordering[Double] {
398+
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
399+
}
400+
implicit object TotalOrdering extends TotalOrdering
401+
402+
/** An ordering for `Double`s which is consistent with IEEE specifications
403+
* whenever possible.
404+
*
405+
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
406+
* comparison operations for `Double`s, and return `false` when called with
407+
* `NaN`.
408+
* - `min` and `max` are consistent with `math.min` and `math.max`, and
409+
* return `NaN` when called with `NaN` as either argument.
410+
* - `compare` behaves the same as [[java.lang.Double.compare()]].
411+
*
412+
* $doubleOrdering
413+
*
414+
* This ordering may be preferable for numeric contexts.
415+
*
416+
* @see [[TotalOrdering]]
417+
*/
418+
trait IeeeOrdering extends Ordering[Double] {
419+
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
420+
421+
override def lteq(x: Double, y: Double): Boolean = x <= y
422+
override def gteq(x: Double, y: Double): Boolean = x >= y
423+
override def lt(x: Double, y: Double): Boolean = x < y
424+
override def gt(x: Double, y: Double): Boolean = x > y
425+
override def equiv(x: Double, y: Double): Boolean = x == y
426+
override def max(x: Double, y: Double): Double = math.max(x, y)
427+
override def min(x: Double, y: Double): Double = math.min(x, y)
428+
}
429+
implicit object IeeeOrdering extends IeeeOrdering
337430
}
338-
implicit object Double extends DoubleOrdering
431+
@deprecated("There are multiple ways to order Doubles (Ordering.Double.TotalOrdering, " +
432+
"Ordering.Double.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it " +
433+
"explicitly. See the documentation for details.", since = "2.13.0")
434+
implicit object DeprecatedDoubleOrdering extends Double.TotalOrdering
339435

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

src/library/scala/runtime/RichDouble.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 RichDouble(val self: Double) extends AnyVal with FractionalProxy[Double] {
1313
protected def num: Fractional[Double] = scala.math.Numeric.DoubleIsFractional
14-
protected def ord: Ordering[Double] = scala.math.Ordering.Double
14+
protected def ord: Ordering[Double] = scala.math.Ordering.Double.TotalOrdering
1515

1616
override def doubleValue() = self
1717
override def floatValue() = self.toFloat

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: Fractional[Float] = scala.math.Numeric.FloatIsFractional
14-
protected def ord: Ordering[Float] = scala.math.Ordering.Float
14+
protected def ord: Ordering[Float] = scala.math.Ordering.Float.TotalOrdering
1515

1616
override def doubleValue() = self.toDouble
1717
override def floatValue() = self

test/files/pos/t10511.check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
t10511.scala:2: warning: object DeprecatedFloatOrdering in object Ordering is deprecated (since 2.13.0): There are multiple ways to order Floats (Ordering.Float.TotalOrdering, Ordering.Float.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it explicitly. See the documentation for details.
2+
val f = Ordering[Float]
3+
^
4+
t10511.scala:3: warning: object DeprecatedDoubleOrdering in object Ordering is deprecated (since 2.13.0): There are multiple ways to order Doubles (Ordering.Double.TotalOrdering, Ordering.Double.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it explicitly. See the documentation for details.
5+
val d = Ordering[Double]
6+
^
7+
t10511.scala:6: warning: object DeprecatedDoubleOrdering in object Ordering is deprecated (since 2.13.0): There are multiple ways to order Doubles (Ordering.Double.TotalOrdering, Ordering.Double.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it explicitly. See the documentation for details.
8+
list.sorted
9+
^

test/files/pos/t10511.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-deprecation

test/files/pos/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/files/run/OrderingTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import scala.math.Ordering.Double.TotalOrdering
2+
13
object Test extends App {
24
def test[T](t1 : T, t2 : T)(implicit ord : Ordering[T]) = {
35
val cmp = ord.compare(t1, t2);

test/files/run/t5857.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
import scala.math.Ordering.Double.IeeeOrdering
22

33

44
object Test {

test/junit/scala/math/OrderingTest.scala

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

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

1142
/* Test for scala/bug#9077 */
1243
@Test
@@ -46,8 +77,8 @@ class OrderingTest {
4677
checkAll[Char](Char.MinValue, -1.toChar, 0.toChar, 1.toChar, Char.MaxValue)
4778
checkAll[Short](Short.MinValue, -1, 0, 1, Short.MaxValue)
4879
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)
80+
checkAll[Double](doubles: _*)
81+
checkAll[Float](floats: _*)
5182

5283
checkAll[BigInt](Int.MinValue, -1, 0, 1, Int.MaxValue)
5384
checkAll[BigDecimal](Int.MinValue, -1, -0, 1, Int.MaxValue)
@@ -76,5 +107,97 @@ class OrderingTest {
76107
check(o1, o2)
77108
check(o1, o3)
78109
}
79-
}
80110

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

0 commit comments

Comments
 (0)