Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug#10511 Add total orderings for Float and Double #6410

Merged
merged 1 commit into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/library/scala/math/Numeric.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ object Numeric {
// logic in Numeric base trait mishandles abs(-0.0f)
override def abs(x: Float): Float = math.abs(x)
}
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering
implicit object FloatIsFractional extends FloatIsFractional with Ordering.Float.IeeeOrdering

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

trait BigDecimalIsConflicted extends Numeric[BigDecimal] {
def plus(x: BigDecimal, y: BigDecimal): BigDecimal = x + y
Expand Down
142 changes: 119 additions & 23 deletions src/library/scala/math/Ordering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ package scala
package math

import java.util.Comparator
import scala.language.{implicitConversions, higherKinds}

import scala.annotation.implicitAmbiguous
import scala.language.{higherKinds, implicitConversions}

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

trait FloatOrdering extends Ordering[Float] {
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
/** `Ordering`s for `Float`s.
*
* @define floatOrdering Because the behaviour of `Float`s specified by IEEE is
* not consistent with a total ordering when dealing with
* `NaN`, there are two orderings defined for `Float`:
* `TotalOrdering`, which is consistent with a total
* ordering, and `IeeeOrdering`, which is consistent
* as much as possible with IEEE spec and floating point
* operations defined in [[scala.math]].
*/
object Float {
/** An ordering for `Float`s which is a fully consistent total ordering,
* and treats `NaN` as larger than all other `Float` values; it behaves
* the same as [[java.lang.Float.compare()]].
*
* $floatOrdering
*
* This ordering may be preferable for sorting collections.
*
* @see [[IeeeOrdering]]
*/
trait TotalOrdering extends Ordering[Float] {
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)
}
implicit object TotalOrdering extends TotalOrdering

override def lteq(x: Float, y: Float): Boolean = x <= y
override def gteq(x: Float, y: Float): Boolean = x >= y
override def lt(x: Float, y: Float): Boolean = x < y
override def gt(x: Float, y: Float): Boolean = x > y
override def equiv(x: Float, y: Float): Boolean = x == y
override def max(x: Float, y: Float): Float = math.max(x, y)
override def min(x: Float, y: Float): Float = math.min(x, y)
/** An ordering for `Float`s which is consistent with IEEE specifications
* whenever possible.
*
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
* comparison operations for `Float`s, and return `false` when called with
* `NaN`.
* - `min` and `max` are consistent with `math.min` and `math.max`, and
* return `NaN` when called with `NaN` as either argument.
* - `compare` behaves the same as [[java.lang.Float.compare()]].
*
* $floatOrdering
*
* This ordering may be preferable for numeric contexts.
*
* @see [[TotalOrdering]]
*/
trait IeeeOrdering extends Ordering[Float] {
def compare(x: Float, y: Float) = java.lang.Float.compare(x, y)

override def lteq(x: Float, y: Float): Boolean = x <= y
override def gteq(x: Float, y: Float): Boolean = x >= y
override def lt(x: Float, y: Float): Boolean = x < y
override def gt(x: Float, y: Float): Boolean = x > y
override def equiv(x: Float, y: Float): Boolean = x == y
override def max(x: Float, y: Float): Float = math.max(x, y)
override def min(x: Float, y: Float): Float = math.min(x, y)
}
implicit object IeeeOrdering extends IeeeOrdering
}
implicit object Float extends FloatOrdering

trait DoubleOrdering extends Ordering[Double] {
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)

override def lteq(x: Double, y: Double): Boolean = x <= y
override def gteq(x: Double, y: Double): Boolean = x >= y
override def lt(x: Double, y: Double): Boolean = x < y
override def gt(x: Double, y: Double): Boolean = x > y
override def equiv(x: Double, y: Double): Boolean = x == y
override def max(x: Double, y: Double): Double = math.max(x, y)
override def min(x: Double, y: Double): Double = math.min(x, y)
@deprecated("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.", since = "2.13.0")
implicit object DeprecatedFloatOrdering extends Float.TotalOrdering

/** `Ordering`s for `Double`s.
*
* @define doubleOrdering Because the behaviour of `Double`s specified by IEEE is
* not consistent with a total ordering when dealing with
* `NaN`, there are two orderings defined for `Double`:
* `TotalOrdering`, which is consistent with a total
* ordering, and `IeeeOrdering`, which is consistent
* as much as possible with IEEE spec and floating point
* operations defined in [[scala.math]].
*/
object Double {
/** An ordering for `Double`s which is a fully consistent total ordering,
* and treats `NaN` as larger than all other `Double` values; it behaves
* the same as [[java.lang.Double.compare()]].
*
* $doubleOrdering
*
* This ordering may be preferable for sorting collections.
*
* @see [[IeeeOrdering]]
*/
trait TotalOrdering extends Ordering[Double] {
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)
}
implicit object TotalOrdering extends TotalOrdering

/** An ordering for `Double`s which is consistent with IEEE specifications
* whenever possible.
*
* - `lt`, `lteq`, `equiv`, `gteq` and `gt` are consistent with primitive
* comparison operations for `Double`s, and return `false` when called with
* `NaN`.
* - `min` and `max` are consistent with `math.min` and `math.max`, and
* return `NaN` when called with `NaN` as either argument.
* - `compare` behaves the same as [[java.lang.Double.compare()]].
*
* $doubleOrdering
*
* This ordering may be preferable for numeric contexts.
*
* @see [[TotalOrdering]]
*/
trait IeeeOrdering extends Ordering[Double] {
def compare(x: Double, y: Double) = java.lang.Double.compare(x, y)

override def lteq(x: Double, y: Double): Boolean = x <= y
override def gteq(x: Double, y: Double): Boolean = x >= y
override def lt(x: Double, y: Double): Boolean = x < y
override def gt(x: Double, y: Double): Boolean = x > y
override def equiv(x: Double, y: Double): Boolean = x == y
override def max(x: Double, y: Double): Double = math.max(x, y)
override def min(x: Double, y: Double): Double = math.min(x, y)
}
implicit object IeeeOrdering extends IeeeOrdering
}
implicit object Double extends DoubleOrdering
@deprecated("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.", since = "2.13.0")
implicit object DeprecatedDoubleOrdering extends Double.TotalOrdering

trait BigIntOrdering extends Ordering[BigInt] {
def compare(x: BigInt, y: BigInt) = x.compare(y)
Expand Down
2 changes: 1 addition & 1 deletion src/library/scala/runtime/RichDouble.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package runtime

final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Double] {
protected def num: Fractional[Double] = scala.math.Numeric.DoubleIsFractional
protected def ord: Ordering[Double] = scala.math.Ordering.Double
protected def ord: Ordering[Double] = scala.math.Ordering.Double.TotalOrdering

override def doubleValue() = self
override def floatValue() = self.toFloat
Expand Down
2 changes: 1 addition & 1 deletion src/library/scala/runtime/RichFloat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package runtime

final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float] {
protected def num: Fractional[Float] = scala.math.Numeric.FloatIsFractional
protected def ord: Ordering[Float] = scala.math.Ordering.Float
protected def ord: Ordering[Float] = scala.math.Ordering.Float.TotalOrdering

override def doubleValue() = self.toDouble
override def floatValue() = self
Expand Down
9 changes: 9 additions & 0 deletions test/files/pos/t10511.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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.
val f = Ordering[Float]
^
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.
val d = Ordering[Double]
^
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.
list.sorted
^
1 change: 1 addition & 0 deletions test/files/pos/t10511.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-deprecation
7 changes: 7 additions & 0 deletions test/files/pos/t10511.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
object Test {
val f = Ordering[Float]
val d = Ordering[Double]

val list = List(1.0, 2.0, 3.0)
list.sorted
}
2 changes: 2 additions & 0 deletions test/files/run/OrderingTest.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.math.Ordering.Double.TotalOrdering

object Test extends App {
def test[T](t1 : T, t2 : T)(implicit ord : Ordering[T]) = {
val cmp = ord.compare(t1, t2);
Expand Down
2 changes: 1 addition & 1 deletion test/files/run/t5857.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

import scala.math.Ordering.Double.IeeeOrdering


object Test {
Expand Down
129 changes: 126 additions & 3 deletions test/junit/scala/math/OrderingTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,39 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

import scala.math.Ordering.Float.TotalOrdering
import scala.math.Ordering.Double.TotalOrdering

import java.{lang => jl}

@RunWith(classOf[JUnit4])
class OrderingTest {
val floats = Seq(
Float.NegativeInfinity,
Float.MinValue,
-1f,
-0f,
0f,
Float.MinPositiveValue,
1f,
Float.MaxValue,
Float.PositiveInfinity,
Float.NaN
)

val doubles = Seq(
Double.NegativeInfinity,
Double.MinValue,
-1d,
-0d,
0d,
Double.MinPositiveValue,
1d,
Double.MaxValue,
Double.PositiveInfinity,
Double.NaN
)


/* Test for scala/bug#9077 */
@Test
Expand Down Expand Up @@ -46,8 +77,8 @@ class OrderingTest {
checkAll[Char](Char.MinValue, -1.toChar, 0.toChar, 1.toChar, Char.MaxValue)
checkAll[Short](Short.MinValue, -1, 0, 1, Short.MaxValue)
checkAll[Int](Int.MinValue, -1, 0, 1, Int.MaxValue)
checkAll[Double](Double.MinValue, -1, -0, 0, 1, Double.MaxValue)
checkAll[Float](Float.MinValue, -1, -0, 0, 1, Float.MaxValue)
checkAll[Double](doubles: _*)
checkAll[Float](floats: _*)

checkAll[BigInt](Int.MinValue, -1, 0, 1, Int.MaxValue)
checkAll[BigDecimal](Int.MinValue, -1, -0, 1, Int.MaxValue)
Expand Down Expand Up @@ -76,5 +107,97 @@ class OrderingTest {
check(o1, o2)
check(o1, o3)
}
}

/* Test for scala/bug#10511 */
@Test
def testFloatDoubleTotalOrdering(): Unit = {
val fNegZeroBits = jl.Float.floatToRawIntBits(-0.0f)
val fPosZeroBits = jl.Float.floatToRawIntBits(0.0f)

val dNegZeroBits = jl.Double.doubleToRawLongBits(-0.0d)
val dPosZeroBits = jl.Double.doubleToRawLongBits(0.0d)

def checkFloats(floats: Float*): Unit = {
def same(f1: Float, f2: Float): Boolean = {
val thisBits = jl.Float.floatToRawIntBits(f1)
if (thisBits == fNegZeroBits) jl.Float.floatToRawIntBits(f2) == fNegZeroBits
else if (thisBits == fPosZeroBits) jl.Float.floatToRawIntBits(f2) == fPosZeroBits
else f1 == f2 || (jl.Float.isNaN(f1) && jl.Float.isNaN(f2))
}

val O = Ordering[Float]
for (i <- floats; j <- floats) {
val msg = s"for i=$i, j=$j"

// consistency with `compare`
assertEquals(msg, O.compare(i, j) < 0, O.lt(i, j))
assertEquals(msg, O.compare(i, j) <= 0, O.lteq(i, j))
assertEquals(msg, O.compare(i, j) == 0, O.equiv(i, j))
assertEquals(msg, O.compare(i, j) >= 0, O.gteq(i, j))
assertEquals(msg, O.compare(i, j) > 0, O.gt(i, j))

// consistency with other ops
assertTrue(msg, O.lteq(i, j) || O.gteq(i, j))
assertTrue(msg, O.lteq(i, j) || O.gt(i, j))
assertTrue(msg, O.lteq(i, j) != O.gt(i, j))
assertTrue(msg, O.lt(i, j) || O.gteq(i, j))
assertTrue(msg, O.lt(i, j) != O.gteq(i, j))
// exactly one of `lt`, `equiv`, `gt` is true
assertTrue(msg,
(O.lt(i, j) ^ O.equiv(i, j) ^ O.gt(i, j))
&& !(O.lt(i, j) && O.equiv(i, j) && O.gt(i, j)))

// consistency with `max` and `min`
assertEquals(msg, O.compare(i, j) >= 0, same(O.max(i, j), i))
assertEquals(msg, O.compare(i, j) <= 0, same(O.min(i, j), i))
if (!same(i, j)) {
assertEquals(msg, O.compare(i, j) < 0, same(O.max(i, j), j))
assertEquals(msg, O.compare(i, j) > 0, same(O.min(i, j), j))
}
}
}

def checkDoubles(doubles: Double*): Unit = {
def same(d1: Double, d2: Double): Boolean = {
val thisBits = jl.Double.doubleToRawLongBits(d1)
if (thisBits == dNegZeroBits) jl.Double.doubleToRawLongBits(d2) == dNegZeroBits
else if (thisBits == dPosZeroBits) jl.Double.doubleToRawLongBits(d2) == dPosZeroBits
else d1 == d2 || (jl.Double.isNaN(d1) && jl.Double.isNaN(d2))
}

val O = Ordering[Double]
for (i <- doubles; j <- doubles) {
val msg = s"for i=$i, j=$j"

// consistency with `compare`
assertEquals(msg, O.compare(i, j) < 0, O.lt(i, j))
assertEquals(msg, O.compare(i, j) <= 0, O.lteq(i, j))
assertEquals(msg, O.compare(i, j) == 0, O.equiv(i, j))
assertEquals(msg, O.compare(i, j) >= 0, O.gteq(i, j))
assertEquals(msg, O.compare(i, j) > 0, O.gt(i, j))

// consistency with other ops
assertTrue(msg, O.lteq(i, j) || O.gteq(i, j))
assertTrue(msg, O.lteq(i, j) || O.gt(i, j))
assertTrue(msg, O.lteq(i, j) != O.gt(i, j))
assertTrue(msg, O.lt(i, j) || O.gteq(i, j))
assertTrue(msg, O.lt(i, j) != O.gteq(i, j))
// exactly one of `lt`, `equiv`, `gt` is true
assertTrue(msg,
(O.lt(i, j) ^ O.equiv(i, j) ^ O.gt(i, j))
&& !(O.lt(i, j) && O.equiv(i, j) && O.gt(i, j)))

// consistency with `max` and `min`
assertEquals(msg, O.compare(i, j) >= 0, same(O.max(i, j), i))
assertEquals(msg, O.compare(i, j) <= 0, same(O.min(i, j), i))
if (!same(i, j)) {
assertEquals(msg, O.compare(i, j) < 0, same(O.max(i, j), j))
assertEquals(msg, O.compare(i, j) > 0, same(O.min(i, j), j))
}
}
}

checkFloats(floats: _*)
checkDoubles(doubles: _*)
}
}