Skip to content

Commit e41fae8

Browse files
committed
Remove Float range and Double range
Fixes scala/bug#10781 At the basis of range is an assumption that summation works reliably. Unfortunately it is not true for IEEE floating point number where numbers are stored as approximate fraction of binary numbers. Natually, Double and Float ranges are completely broken. See numbers of issues such as scala/bug#8518, scala/bug#9875, scala/bug#9874, scala/bug#8620, scala/bug#8670, and scala/bug#10759. I've attempted to fix the Double range in scala/collection-strawman#489 by faking it further using BigDecimal, but ultimately I was not able to overcome the fact that IEEE floats are not suited for ranges. This removes both Float and Double ranges, as well as the fake `Integral[T]` instances that underpin their existence.
1 parent 571f975 commit e41fae8

File tree

16 files changed

+18
-164
lines changed

16 files changed

+18
-164
lines changed

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,18 +241,6 @@ sealed class NumericRange[T](
241241
}
242242
ans.asInstanceOf[B]
243243
}
244-
else if ((num eq scala.math.Numeric.FloatAsIfIntegral) ||
245-
(num eq scala.math.Numeric.DoubleAsIfIntegral)) {
246-
// Try to compute sum with reasonable accuracy, avoiding over/underflow
247-
val numAsIntegral = num.asInstanceOf[Integral[B]]
248-
import numAsIntegral._
249-
val a = math.abs(head.toDouble)
250-
val b = math.abs(last.toDouble)
251-
val two = num fromInt 2
252-
val nre = num fromInt size
253-
if (a > 1e38 || b > 1e38) nre * ((head / two) + (last / two)) // Compute in parts to avoid Infinity if possible
254-
else (nre / two) * (head + last) // Don't need to worry about infinity; this will be more accurate and avoid underflow
255-
}
256244
else if ((num eq scala.math.Numeric.BigIntIsIntegral) ||
257245
(num eq scala.math.Numeric.BigDecimalIsFractional)) {
258246
// No overflow, so we can use arithmetic series formula directly
@@ -423,8 +411,6 @@ object NumericRange {
423411
Numeric.ByteIsIntegral -> Ordering.Byte,
424412
Numeric.CharIsIntegral -> Ordering.Char,
425413
Numeric.LongIsIntegral -> Ordering.Long,
426-
Numeric.FloatAsIfIntegral -> Ordering.Float,
427-
Numeric.DoubleAsIfIntegral -> Ordering.Double,
428414
Numeric.BigDecimalAsIfIntegral -> Ordering.BigDecimal
429415
)
430416

src/library/scala/collection/immutable/Range.scala

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -475,24 +475,6 @@ object Range {
475475
NumericRange.inclusive(start, end, step)
476476
}
477477

478-
// Double works by using a BigDecimal under the hood for precise
479-
// stepping, but mapping the sequence values back to doubles with
480-
// .doubleValue. This constructs the BigDecimals by way of the
481-
// String constructor (valueOf) instead of the Double one, which
482-
// is necessary to keep 0.3d at 0.3 as opposed to
483-
// 0.299999999999999988897769753748434595763683319091796875 or so.
484-
object Double {
485-
implicit val bigDecAsIntegral: Numeric.BigDecimalAsIfIntegral = Numeric.BigDecimalAsIfIntegral
486-
implicit val doubleAsIntegral: Numeric.DoubleAsIfIntegral = Numeric.DoubleAsIfIntegral
487-
def toBD(x: Double): BigDecimal = scala.math.BigDecimal valueOf x
488-
489-
def apply(start: Double, end: Double, step: Double) =
490-
BigDecimal(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue)
491-
492-
def inclusive(start: Double, end: Double, step: Double) =
493-
BigDecimal.inclusive(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue)
494-
}
495-
496478
// As there is no appealing default step size for not-really-integral ranges,
497479
// we offer a partially constructed object.
498480
class Partial[T, U](private val f: T => U) extends AnyVal {

src/library/scala/math/Numeric.scala

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ object Numeric {
126126
}
127127
implicit object LongIsIntegral extends LongIsIntegral with Ordering.LongOrdering
128128

129-
trait FloatIsConflicted extends Numeric[Float] {
129+
trait FloatIsFractional extends Fractional[Float] {
130130
def plus(x: Float, y: Float): Float = x + y
131131
def minus(x: Float, y: Float): Float = x - y
132132
def times(x: Float, y: Float): Float = x * y
@@ -137,21 +137,13 @@ object Numeric {
137137
def toLong(x: Float): Long = x.toLong
138138
def toFloat(x: Float): Float = x
139139
def toDouble(x: Float): Double = x.toDouble
140+
def div(x: Float, y: Float): Float = x / y
140141
// logic in Numeric base trait mishandles abs(-0.0f)
141142
override def abs(x: Float): Float = math.abs(x)
142143
}
143-
trait FloatIsFractional extends FloatIsConflicted with Fractional[Float] {
144-
def div(x: Float, y: Float): Float = x / y
145-
}
146-
trait FloatAsIfIntegral extends FloatIsConflicted with Integral[Float] {
147-
def quot(x: Float, y: Float): Float = (BigDecimal(x.toDouble) quot BigDecimal(y.toDouble)).floatValue
148-
def rem(x: Float, y: Float): Float = (BigDecimal(x.toDouble) remainder BigDecimal(y.toDouble)).floatValue
149-
}
150144
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering
151-
object FloatAsIfIntegral extends FloatAsIfIntegral with Ordering.FloatOrdering {
152-
}
153145

154-
trait DoubleIsConflicted extends Numeric[Double] {
146+
trait DoubleIsFractional extends Fractional[Double] {
155147
def plus(x: Double, y: Double): Double = x + y
156148
def minus(x: Double, y: Double): Double = x - y
157149
def times(x: Double, y: Double): Double = x * y
@@ -162,16 +154,11 @@ object Numeric {
162154
def toLong(x: Double): Long = x.toLong
163155
def toFloat(x: Double): Float = x.toFloat
164156
def toDouble(x: Double): Double = x
157+
def div(x: Double, y: Double): Double = x / y
165158
// logic in Numeric base trait mishandles abs(-0.0)
166159
override def abs(x: Double): Double = math.abs(x)
167160
}
168-
trait DoubleIsFractional extends DoubleIsConflicted with Fractional[Double] {
169-
def div(x: Double, y: Double): Double = x / y
170-
}
171-
trait DoubleAsIfIntegral extends DoubleIsConflicted with Integral[Double] {
172-
def quot(x: Double, y: Double): Double = (BigDecimal(x) quot BigDecimal(y)).doubleValue
173-
def rem(x: Double, y: Double): Double = (BigDecimal(x) remainder BigDecimal(y)).doubleValue
174-
}
161+
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering
175162

176163
trait BigDecimalIsConflicted extends Numeric[BigDecimal] {
177164
def plus(x: BigDecimal, y: BigDecimal): BigDecimal = x + y
@@ -194,13 +181,10 @@ object Numeric {
194181
def rem(x: BigDecimal, y: BigDecimal): BigDecimal = x remainder y
195182
}
196183

197-
// For Double and BigDecimal we offer implicit Fractional objects, but also one
184+
// For BigDecimal we offer an implicit Fractional object, but also one
198185
// which acts like an Integral type, which is useful in NumericRange.
199186
implicit object BigDecimalIsFractional extends BigDecimalIsFractional with Ordering.BigDecimalOrdering
200187
object BigDecimalAsIfIntegral extends BigDecimalAsIfIntegral with Ordering.BigDecimalOrdering
201-
202-
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering
203-
object DoubleAsIfIntegral extends DoubleAsIfIntegral with Ordering.DoubleOrdering
204188
}
205189

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

src/library/scala/runtime/RichDouble.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +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
15-
protected def integralNum = scala.math.Numeric.DoubleAsIfIntegral
13+
protected def num: Fractional[Double] = scala.math.Numeric.DoubleIsFractional
14+
protected def ord: Ordering[Double] = scala.math.Ordering.Double
1615

1716
override def doubleValue() = self
1817
override def floatValue() = self.toFloat

src/library/scala/runtime/RichFloat.scala

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

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

1716
override def doubleValue() = self.toDouble
1817
override def floatValue() = self

src/library/scala/runtime/ScalaNumberProxy.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,10 @@ trait IntegralProxy[T] extends Any with ScalaWholeNumberProxy[T] with RangedProx
5353
def to(end: T): NumericRange.Inclusive[T] = NumericRange.inclusive(self, end, num.one)
5454
def to(end: T, step: T): NumericRange.Inclusive[T] = NumericRange.inclusive(self, end, step)
5555
}
56-
trait FractionalProxy[T] extends Any with ScalaNumberProxy[T] with RangedProxy[T] {
56+
trait FractionalProxy[T] extends Any with ScalaNumberProxy[T] {
5757
protected implicit def num: Fractional[T]
58-
protected implicit def integralNum: Integral[T]
59-
60-
/** In order to supply predictable ranges, we require an Integral[T] which provides
61-
* us with discrete operations on the (otherwise fractional) T. See Numeric.DoubleAsIfIntegral
62-
* for an example.
63-
*/
64-
type ResultWithoutStep = Range.Partial[T, NumericRange[T]]
6558

6659
def isWhole() = false
67-
def until(end: T): ResultWithoutStep = new Range.Partial(NumericRange(self, end, _))
68-
def until(end: T, step: T): NumericRange.Exclusive[T] = NumericRange(self, end, step)
69-
def to(end: T): ResultWithoutStep = new Range.Partial(NumericRange.inclusive(self, end, _))
70-
def to(end: T, step: T): NumericRange.Inclusive[T] = NumericRange.inclusive(self, end, step)
7160
}
7261

7362
trait OrderedProxy[T] extends Any with Ordered[T] with Typed[T] {

test/files/presentation/infix-completion.check

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ reload: Snippet.scala
33
askTypeCompletion at Snippet.scala(1,34)
44
================================================================================
55
[response] askTypeCompletion at (1,34)
6-
retrieved 211 members
7-
[inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type
8-
[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type
9-
[inaccessible] protected def ord: math.Ordering.Double.type
6+
retrieved 202 members
7+
[inaccessible] protected def num: Fractional[Double]
8+
[inaccessible] protected def ord: Ordering[Double]
109
[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean
1110
[inaccessible] protected def unifiedPrimitiveHashcode(): Int
1211
[inaccessible] protected[package lang] def clone(): Object
@@ -140,10 +139,6 @@ def isValidLong: Boolean
140139
def longValue(): Long
141140
def round: Long
142141
def shortValue(): Short
143-
def to(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]]
144-
def to(end: Double,step: Double): scala.collection.immutable.NumericRange.Inclusive[Double]
145-
def to(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]]
146-
def to(end: Float,step: Float): scala.collection.immutable.NumericRange.Inclusive[Float]
147142
def to(end: Int): scala.collection.immutable.Range.Inclusive
148143
def to(end: Int,step: Int): scala.collection.immutable.Range.Inclusive
149144
def to(end: Long): scala.collection.immutable.NumericRange.Inclusive[Long]
@@ -165,10 +160,6 @@ def unary_+: Int
165160
def unary_-: Int
166161
def unary_~: Int
167162
def underlying(): AnyRef
168-
def until(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]]
169-
def until(end: Double,step: Double): scala.collection.immutable.NumericRange.Exclusive[Double]
170-
def until(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]]
171-
def until(end: Float,step: Float): scala.collection.immutable.NumericRange.Exclusive[Float]
172163
def until(end: Int): scala.collection.immutable.Range
173164
def until(end: Int,step: Int): scala.collection.immutable.Range
174165
def until(end: Long): scala.collection.immutable.NumericRange.Exclusive[Long]

test/files/presentation/infix-completion2.check

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ reload: Snippet.scala
33
askTypeCompletion at Snippet.scala(1,34)
44
================================================================================
55
[response] askTypeCompletion at (1,34)
6-
retrieved 211 members
7-
[inaccessible] protected def integralNum: math.Numeric.DoubleAsIfIntegral.type
8-
[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type
9-
[inaccessible] protected def ord: math.Ordering.Double.type
6+
retrieved 202 members
7+
[inaccessible] protected def num: Fractional[Double]
8+
[inaccessible] protected def ord: Ordering[Double]
109
[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean
1110
[inaccessible] protected def unifiedPrimitiveHashcode(): Int
1211
[inaccessible] protected[package lang] def clone(): Object
@@ -140,10 +139,6 @@ def isValidLong: Boolean
140139
def longValue(): Long
141140
def round: Long
142141
def shortValue(): Short
143-
def to(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]]
144-
def to(end: Double,step: Double): scala.collection.immutable.NumericRange.Inclusive[Double]
145-
def to(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]]
146-
def to(end: Float,step: Float): scala.collection.immutable.NumericRange.Inclusive[Float]
147142
def to(end: Int): scala.collection.immutable.Range.Inclusive
148143
def to(end: Int,step: Int): scala.collection.immutable.Range.Inclusive
149144
def to(end: Long): scala.collection.immutable.NumericRange.Inclusive[Long]
@@ -165,10 +160,6 @@ def unary_+: Int
165160
def unary_-: Int
166161
def unary_~: Int
167162
def underlying(): AnyRef
168-
def until(end: Double): Range.Partial[Double,scala.collection.immutable.NumericRange[Double]]
169-
def until(end: Double,step: Double): scala.collection.immutable.NumericRange.Exclusive[Double]
170-
def until(end: Float): Range.Partial[Float,scala.collection.immutable.NumericRange[Float]]
171-
def until(end: Float,step: Float): scala.collection.immutable.NumericRange.Exclusive[Float]
172163
def until(end: Int): scala.collection.immutable.Range
173164
def until(end: Int,step: Int): scala.collection.immutable.Range
174165
def until(end: Long): scala.collection.immutable.NumericRange.Exclusive[Long]

test/files/run/range.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,12 @@ object Test {
5454

5555
def main(args: Array[String]): Unit = {
5656
implicit val imp1 = Numeric.BigDecimalAsIfIntegral
57-
implicit val imp2 = Numeric.DoubleAsIfIntegral
5857

5958
val _grs = List[GR[_]](
6059
GR(BigDecimal(5.0)),
6160
GR(BigDecimal(0.25)), // scala/bug#9348
6261
GR(BigInt(5)),
6362
GR(5L),
64-
GR(5.0d),
6563
GR(2.toByte)
6664
)
6765
val grs = _grs ::: (_grs map (_.negated))

test/files/run/t3518.scala

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/files/run/t4201.scala

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/files/run/t5857.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@ object Test {
2121
val descending = sz to 1 by -1
2222
check { assert(descending.min == 1) }
2323
check { assert(descending.max == sz) }
24-
25-
val numeric = 1.0 to sz.toDouble by 1
26-
check { assert(numeric.min == 1.0) }
27-
check { assert(numeric.max == sz.toDouble) }
28-
29-
val numdesc = sz.toDouble to 1.0 by -1
30-
check { assert(numdesc.min == 1.0) }
31-
check { assert(numdesc.max == sz.toDouble) }
3224
}
3325

3426
def check[U](b: =>U) {

test/files/run/t9656.check

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,5 @@ empty Range 100 until 100
88
NumericRange 1 to 10
99
NumericRange 1 to 10 by 2
1010
NumericRange 0.1 until 1.0 by 0.1
11-
NumericRange 0.1 until 1.0 by 0.1
12-
NumericRange 0.1 until 1.0 by 0.1 (using NumericRange 0.1 until 1.0 by 0.1 of BigDecimal)
1311
NumericRange 0 days until 10 seconds by 1 second
1412
empty NumericRange 0 days until 0 days by 1 second

test/files/run/t9656.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ object Test extends App {
1313
println(1L to 10L)
1414
println(1L to 10L by 2)
1515

16-
// want to know if this is BigDecimal or Double stepping by BigDecimal
17-
println(0.1 until 1.0 by 0.1)
16+
// want to know if this is BigDecimal stepping by BigDecimal
1817
println(Range.BigDecimal(BigDecimal("0.1"), BigDecimal("1.0"), BigDecimal("0.1")))
19-
println(Range.Double(0.1, 1.0, 0.1))
2018

2119
import concurrent.duration.{SECONDS => Seconds, _}, collection.immutable.NumericRange
2220
implicit val `duration is integerish`: math.Integral[FiniteDuration] = new math.Integral[FiniteDuration] {

test/junit/scala/collection/immutable/RangeConsistencyTest.scala

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,6 @@ class RangeConsistencyTest {
137137
assert( (-3 to Int.MaxValue).dropWhile(_ <= 0).length == Int.MaxValue )
138138
assert( (-3 to Int.MaxValue).span(_ <= 0) match { case (a,b) => a.length == 4 && b.length == Int.MaxValue } )
139139
}
140-
141-
@Test
142-
def testSI9348() {
143-
// Test exclusive range with (end-start) != 0 (mod step)
144-
assert( (0.0f until 0.4f by 0.25f) sameElements List(0.0f, 0.25f) )
145-
assert( (1.0 until 2.2 by 0.5) sameElements List(1.0, 1.5, 2.0) )
146-
147-
def bd(d: Double) = BigDecimal(d)
148-
val bdRange = bd(-10.0) until bd(0.0) by bd(4.5)
149-
assert( bdRange sameElements List(bd(-10.0), bd(-5.5), bd(-1.0)) )
150-
}
151140

152141
@Test
153142
def test_SI9388() {

test/junit/scala/math/NumericTest.scala

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ import org.junit.Test
55
import org.junit.runner.RunWith
66
import org.junit.runners.JUnit4
77

8-
import scala.math.Numeric.FloatAsIfIntegral
9-
10-
118
@RunWith(classOf[JUnit4])
129
class NumericTest {
1310

@@ -17,22 +14,6 @@ class NumericTest {
1714
assertTrue(-0.0.abs equals 0.0)
1815
assertTrue(-0.0f.abs equals 0.0f)
1916
}
20-
21-
/* Test for scala/bug#9348 */
22-
@Test
23-
def testFloatAsIfIntegral {
24-
val num = scala.math.Numeric.FloatAsIfIntegral
25-
assertTrue(num.quot(1.0f, 0.5f) equals 2.0f)
26-
assertTrue(num.quot(1.0f, 0.3f) equals 3.0f)
27-
}
28-
29-
/* Test for scala/bug#9348 */
30-
@Test
31-
def testDoubleAsIfIntegral {
32-
val num = scala.math.Numeric.DoubleAsIfIntegral
33-
assertTrue(num.quot(1.0, 0.25) equals 4.0)
34-
assertTrue(num.quot(0.5, 0.15) equals 3.0)
35-
}
3617

3718
/* Test for scala/bug#9348 */
3819
@Test

0 commit comments

Comments
 (0)