Skip to content

Commit c883a7f

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 9291e12 commit c883a7f

16 files changed

+5
-146
lines changed

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,6 @@ extends AbstractSeq[T] with IndexedSeq[T] with Serializable {
212212
}
213213
ans.asInstanceOf[B]
214214
}
215-
else if ((num eq scala.math.Numeric.FloatAsIfIntegral) ||
216-
(num eq scala.math.Numeric.DoubleAsIfIntegral)) {
217-
// Try to compute sum with reasonable accuracy, avoiding over/underflow
218-
val numAsIntegral = num.asInstanceOf[Integral[B]]
219-
import numAsIntegral._
220-
val a = math.abs(head.toDouble)
221-
val b = math.abs(last.toDouble)
222-
val two = num fromInt 2
223-
val nre = num fromInt numRangeElements
224-
if (a > 1e38 || b > 1e38) nre * ((head / two) + (last / two)) // Compute in parts to avoid Infinity if possible
225-
else (nre / two) * (head + last) // Don't need to worry about infinity; this will be more accurate and avoid underflow
226-
}
227215
else if ((num eq scala.math.Numeric.BigIntIsIntegral) ||
228216
(num eq scala.math.Numeric.BigDecimalIsFractional)) {
229217
// No overflow, so we can use arithmetic series formula directly
@@ -390,8 +378,6 @@ object NumericRange {
390378
Numeric.ByteIsIntegral -> Ordering.Byte,
391379
Numeric.CharIsIntegral -> Ordering.Char,
392380
Numeric.LongIsIntegral -> Ordering.Long,
393-
Numeric.FloatAsIfIntegral -> Ordering.Float,
394-
Numeric.DoubleAsIfIntegral -> Ordering.Double,
395381
Numeric.BigDecimalAsIfIntegral -> Ordering.BigDecimal
396382
)
397383

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

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -480,24 +480,6 @@ object Range {
480480
NumericRange.inclusive(start, end, step)
481481
}
482482

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

src/library/scala/math/Numeric.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,7 @@ object Numeric {
143143
trait FloatIsFractional extends FloatIsConflicted with Fractional[Float] {
144144
def div(x: Float, y: Float): Float = x / y
145145
}
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-
}
150146
implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering
151-
object FloatAsIfIntegral extends FloatAsIfIntegral with Ordering.FloatOrdering {
152-
}
153147

154148
trait DoubleIsConflicted extends Numeric[Double] {
155149
def plus(x: Double, y: Double): Double = x + y
@@ -168,10 +162,6 @@ object Numeric {
168162
trait DoubleIsFractional extends DoubleIsConflicted with Fractional[Double] {
169163
def div(x: Double, y: Double): Double = x / y
170164
}
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-
}
175165

176166
trait BigDecimalIsConflicted extends Numeric[BigDecimal] {
177167
def plus(x: BigDecimal, y: BigDecimal): BigDecimal = x + y
@@ -194,13 +184,12 @@ object Numeric {
194184
def rem(x: BigDecimal, y: BigDecimal): BigDecimal = x remainder y
195185
}
196186

197-
// For Double and BigDecimal we offer implicit Fractional objects, but also one
187+
// For BigDecimal we offer implicit Fractional objects, but also one
198188
// which acts like an Integral type, which is useful in NumericRange.
199189
implicit object BigDecimalIsFractional extends BigDecimalIsFractional with Ordering.BigDecimalOrdering
200190
object BigDecimalAsIfIntegral extends BigDecimalAsIfIntegral with Ordering.BigDecimalOrdering
201191

202192
implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering
203-
object DoubleAsIfIntegral extends DoubleAsIfIntegral with Ordering.DoubleOrdering
204193
}
205194

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

src/library/scala/runtime/RichDouble.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ package runtime
1212
final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Double] {
1313
protected def num = scala.math.Numeric.DoubleIsFractional
1414
protected def ord = scala.math.Ordering.Double
15-
protected def integralNum = scala.math.Numeric.DoubleAsIfIntegral
1615

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

src/library/scala/runtime/RichFloat.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ package runtime
1212
final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float] {
1313
protected def num = scala.math.Numeric.FloatIsFractional
1414
protected def ord = scala.math.Ordering.Float
15-
protected def integralNum = scala.math.Numeric.FloatAsIfIntegral
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: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ 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
6+
retrieved 202 members
87
[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type
98
[inaccessible] protected def ord: math.Ordering.Double.type
109
[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean
@@ -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: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ 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
6+
retrieved 202 members
87
[inaccessible] protected def num: math.Numeric.DoubleIsFractional.type
98
[inaccessible] protected def ord: math.Ordering.Double.type
109
[inaccessible] protected def unifiedPrimitiveEquals(x: Any): Boolean
@@ -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)