diff options
-rw-r--r-- | src/library/scala/BigDecimal.scala | 17 | ||||
-rw-r--r-- | src/library/scala/Numeric.scala | 21 | ||||
-rw-r--r-- | src/library/scala/collection/immutable/GenericRange.scala | 67 | ||||
-rw-r--r-- | src/library/scala/collection/immutable/Range.scala | 18 | ||||
-rw-r--r-- | src/library/scala/runtime/RichDouble.scala | 20 | ||||
-rw-r--r-- | test/files/run/range.scala | 54 |
6 files changed, 150 insertions, 47 deletions
diff --git a/src/library/scala/BigDecimal.scala b/src/library/scala/BigDecimal.scala index 39b3f4b571..fc874db1f2 100644 --- a/src/library/scala/BigDecimal.scala +++ b/src/library/scala/BigDecimal.scala @@ -46,6 +46,15 @@ object BigDecimal val defaultMathContext = MathContext.UNLIMITED + /** Constructs a <code>BigDecimal</code> using the java BigDecimal static + * valueOf constructor. + * + * @param d the specified double value + * @return the constructed <code>BigDecimal</code> + */ + def valueOf(d: Double): BigDecimal = apply(BigDec valueOf d) + def valueOf(d: Double, mc: MathContext): BigDecimal = apply(BigDec valueOf d, mc) + /** Constructs a <code>BigDecimal</code> whose value is equal to that of the * specified <code>Integer</code> value. * @@ -144,7 +153,7 @@ object BigDecimal implicit def long2bigDecimal(l: Long): BigDecimal = apply(l) /** Implicit conversion from <code>Double</code> to <code>BigDecimal</code>. */ - implicit def double2bigDecimal(d: Double): BigDecimal = apply(d) + implicit def double2bigDecimal(d: Double): BigDecimal = valueOf(d, defaultMathContext) } /** @@ -337,9 +346,9 @@ extends jl.Number with ScalaNumericConversions def floatValue = this.bigDecimal.floatValue /** Converts this BigDecimal to a <tt>Double</tt>. - * if this BigDecimal has too great a magnitude to represent as a float, - * it will be converted to <code>Float.NEGATIVE_INFINITY</code> or - * <code>Float.POSITIVE_INFINITY</code> as appropriate. + * if this BigDecimal has too great a magnitude to represent as a double, + * it will be converted to <code>Double.NEGATIVE_INFINITY</code> or + * <code>Double.POSITIVE_INFINITY</code> as appropriate. */ def doubleValue = this.bigDecimal.doubleValue diff --git a/src/library/scala/Numeric.scala b/src/library/scala/Numeric.scala index ef3d771d6e..3048f9287d 100644 --- a/src/library/scala/Numeric.scala +++ b/src/library/scala/Numeric.scala @@ -104,11 +104,10 @@ object Numeric { } implicit object FloatIsFractional extends FloatIsFractional with Ordering.FloatOrdering - trait DoubleIsFractional extends Fractional[Double] { + trait DoubleIsConflicted extends Numeric[Double] { def plus(x: Double, y: Double): Double = x + y def minus(x: Double, y: Double): Double = x - y def times(x: Double, y: Double): Double = x * y - def div(x: Double, y: Double): Double = x / y def negate(x: Double): Double = -x def fromInt(x: Int): Double = x def toInt(x: Double): Int = x.toInt @@ -116,7 +115,13 @@ object Numeric { def toFloat(x: Double): Float = x.toFloat def toDouble(x: Double): Double = x } - implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering + trait DoubleIsFractional extends DoubleIsConflicted with Fractional[Double] { + def div(x: Double, y: Double): Double = x / y + } + trait DoubleAsIfIntegral extends DoubleIsConflicted with Integral[Double] { + def quot(x: Double, y: Double): Double = (BigDecimal(x) / BigDecimal(y)).doubleValue + def rem(x: Double, y: Double): Double = (BigDecimal(x) remainder BigDecimal(y)).doubleValue + } trait BigDecimalIsConflicted extends Numeric[BigDecimal] { def plus(x: BigDecimal, y: BigDecimal): BigDecimal = x + y @@ -135,14 +140,16 @@ object Numeric { } trait BigDecimalAsIfIntegral extends BigDecimalIsConflicted with Integral[BigDecimal] { def quot(x: BigDecimal, y: BigDecimal): BigDecimal = x / y - // scala.BigDecimal doesn't give access to remainder, grr - def rem(x: BigDecimal, y: BigDecimal): BigDecimal = - new BigDecimal(x.bigDecimal remainder y.bigDecimal) + def rem(x: BigDecimal, y: BigDecimal): BigDecimal = x remainder y } - // The Fractional one is the implicit, but Integral is useful for GenericRange. + // For Double and BigDecimal we offer implicit Fractional objects, but also one + // which acts like an Integral type, which is useful in GenericRange. implicit object BigDecimalIsFractional extends BigDecimalIsFractional with Ordering.BigDecimalOrdering object BigDecimalAsIfIntegral extends BigDecimalAsIfIntegral with Ordering.BigDecimalOrdering + + implicit object DoubleIsFractional extends DoubleIsFractional with Ordering.DoubleOrdering + object DoubleAsIfIntegral extends DoubleAsIfIntegral with Ordering.DoubleOrdering } trait Numeric[T] extends Ordering[T] { diff --git a/src/library/scala/collection/immutable/GenericRange.scala b/src/library/scala/collection/immutable/GenericRange.scala index 90779912c0..1fd977d7e8 100644 --- a/src/library/scala/collection/immutable/GenericRange.scala +++ b/src/library/scala/collection/immutable/GenericRange.scala @@ -8,13 +8,12 @@ // $Id: GenericRange.scala 18987 2009-10-08 18:31:44Z odersky $ -package scala.collection.immutable +package scala.collection +package immutable import annotation.experimental - -import collection.VectorView -import util.control.Exception.catching -import util.Hashable +import mutable.{ Builder, ListBuffer } +import generic._ /** <p> * <code>GenericRange</code> is a generified version of the @@ -39,15 +38,21 @@ import util.Hashable abstract class GenericRange[+T] (val start: T, val end: T, val step: T, val isInclusive: Boolean) (implicit num: Integral[T]) -extends VectorView[T, collection.immutable.Vector[T]] +extends Vector[T] { import num._ + private def fail(msg: String) = throw new UnsupportedOperationException(msg) + + if (step equiv zero) + fail("GenericRange step cannot be zero.") + // todo? - we could lift the length restriction by implementing a range as a sequence of // subranges and limiting the subranges to MAX_INT. There's no other way around it because // the generics we inherit assume integer-based indexing (as well they should.) - require(!(step equiv zero)) - require(genericLength <= fromInt(Math.MAX_INT), "Implementation restricts ranges to Math.MAX_INT elements.") + // The second condition is making sure type T can meaningfully be compared to Math.MAX_INT. + if (genericLength > fromInt(Math.MAX_INT) && (Math.MAX_INT == toInt(fromInt(Math.MAX_INT)))) + fail("Implementation restricts ranges to Math.MAX_INT elements.") // inclusive/exclusiveness captured this way because we do not have any // concept of a "unit", we can't just add an epsilon to an exclusive @@ -97,7 +102,7 @@ extends VectorView[T, collection.immutable.Vector[T]] } def length: Int = toInt(genericLength) - final override def isEmpty = + override def isEmpty = if (step > zero) if (isInclusive) end < start else end <= start @@ -123,6 +128,46 @@ extends VectorView[T, collection.immutable.Vector[T]] ) } + // Motivated by the desire for Double ranges with BigDecimal precision, + // we need some way to map a Range and get another Range. This can't be + // done in any fully general way because Ranges are not arbitrary + // sequences but step-valued, so we have a custom method only we can call + // which we promise to use responsibly. + // + // The point of it all is that + // + // 0.0 to 1.0 by 0.1 + // + // should result in + // + // GenericRange[Double](0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0) + // + // and not + // + // GenericRange[Double](0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9) + // + // or perhaps more importantly, + // + // (0.1 to 0.3 by 0.1 contains 0.3) == true + // + private[immutable] def mapRange[A](fm: T => A)(implicit unum: Integral[A]): GenericRange[A] = { + val self = this + + // XXX This may be incomplete. + new GenericRange[A](fm(start), fm(end), fm(step), isInclusive) { + def copy[A1 >: A](start: A1, end: A1, step: A1)(implicit unum: Integral[A1]): GenericRange[A1] = + if (isInclusive) GenericRange.inclusive(start, end, step) + else GenericRange(start, end, step) + + private val underlyingRange: GenericRange[T] = self + override def foreach[U](f: A => U) { underlyingRange foreach (x => f(fm(x))) } + override def isEmpty = underlyingRange.isEmpty + override def apply(idx: Int): A = fm(underlyingRange(idx)) + override def containsTyped[A1 >: A](el: A1)(implicit unum: Integral[A1]) = + underlyingRange exists (x => fm(x) == el) + } + } + // The contains situation makes for some interesting code. // I am not aware of any way to avoid a cast somewhere, because // contains must take an Any. @@ -153,9 +198,7 @@ extends VectorView[T, collection.immutable.Vector[T]] } } - -object GenericRange -{ +object GenericRange { class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) extends GenericRange(start, end, step, true) { def copy[U >: T](start: U, end: U, step: U)(implicit unum: Integral[U]): Inclusive[U] = diff --git a/src/library/scala/collection/immutable/Range.scala b/src/library/scala/collection/immutable/Range.scala index 56564a28aa..2b0b7200de 100644 --- a/src/library/scala/collection/immutable/Range.scala +++ b/src/library/scala/collection/immutable/Range.scala @@ -177,10 +177,22 @@ object Range { GenericRange.inclusive(start, end, step) } - // Double re-uses BigDecimal's range. + // Double works by using a BigDecimal under the hood for precise + // stepping, but mapping the sequence values back to doubles with + // .doubleValue. This constructs the BigDecimals by way of the + // String constructor (valueOf) instead of the Double one, which + // is necessary to keep 0.3d at 0.3 as opposed to + // 0.299999999999999988897769753748434595763683319091796875 or so. object Double { - def apply(start: Double, end: Double, step: Double) = scala.BigDecimal(start) until end by step - def inclusive(start: Double, end: Double, step: Double) = scala.BigDecimal(start) to end by step + implicit val bigDecAsIntegral = scala.Numeric.BigDecimalAsIfIntegral + implicit val doubleAsIntegral = scala.Numeric.DoubleAsIfIntegral + def toBD(x: Double): BigDecimal = scala.BigDecimal valueOf x + + def apply(start: Double, end: Double, step: Double) = + BigDecimal(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) + + def inclusive(start: Double, end: Double, step: Double) = + BigDecimal.inclusive(toBD(start), toBD(end), toBD(step)) mapRange (_.doubleValue) } // As there is no appealing default step size for not-really-integral ranges, diff --git a/src/library/scala/runtime/RichDouble.scala b/src/library/scala/runtime/RichDouble.scala index 3d18aefce2..c29d7e0868 100644 --- a/src/library/scala/runtime/RichDouble.scala +++ b/src/library/scala/runtime/RichDouble.scala @@ -27,29 +27,21 @@ final class RichDouble(x: Double) extends Proxy with Ordered[Double] { def ceil: Double = Math.ceil(x) def floor: Double = Math.floor(x) - /** !!! At the time I wrote these Double ranges, BigDecimal(0.17) == 0.17d. Since - * this is no longer true, these ranges need to operate on Doubles, else - * (0.0 to 1.0 by 0.1).contains(0.1) will be false under the anticipated 2.8 - * equality scheme. They only operate on BigDecimals because I wasn't 100% - * sure about when operations on Doubles are guaranteed to be exact and using - * BigDecimal seemed the conservative route. - */ - /** See <code>BigDecimal.until</code>. */ - def until(end: Double): Range.Partial[Double, GenericRange.Exclusive[BigDecimal]] = + def until(end: Double): Range.Partial[Double, GenericRange[Double]] = new Range.Partial(until(end, _)) /** See <code>BigDecimal.until</code>. */ - def until(end: Double, step: Double): GenericRange.Exclusive[BigDecimal] = - BigDecimal(x).until(end, step) + def until(end: Double, step: Double): GenericRange[Double] = + Range.Double(x, end, step) /** See <code>BigDecimal.to</code>. */ - def to(end: Double): Range.Partial[Double, GenericRange.Inclusive[BigDecimal]] = + def to(end: Double): Range.Partial[Double, GenericRange[Double]] = new Range.Partial(to(end, _)) /** See <code>BigDecimal.to</code>. */ - def to(end: Double, step: Double): GenericRange.Inclusive[BigDecimal] = - BigDecimal(x).to(end, step) + def to(end: Double, step: Double): GenericRange[Double] = + Range.Double.inclusive(x, end, step) /** Converts an angle measured in degrees to an approximately equivalent * angle measured in radians. diff --git a/test/files/run/range.scala b/test/files/run/range.scala index 7d30dc30c2..97d7a84acc 100644 --- a/test/files/run/range.scala +++ b/test/files/run/range.scala @@ -1,14 +1,54 @@ -object Test extends Application{ +import scala.collection.immutable.{ Range, GenericRange } + +object Test { def rangeForeach(range : Range) = { val buffer = new scala.collection.mutable.ListBuffer[Int]; range.foreach(buffer += _); assert(buffer.toList == range.iterator.toList, buffer.toList+"/"+range.iterator.toList) } - rangeForeach(1 to 10); - rangeForeach(1 until 10); - rangeForeach(10 to 1 by -1); - rangeForeach(10 until 1 by -1); - rangeForeach(10 to 1 by -3); - rangeForeach(10 until 1 by -3); + case class GR[T](val x: T)(implicit val num: Integral[T]) { + import num._ + + def negated = GR[T](-x) + + def gr1 = GenericRange(x, x, x) + def gr2 = GenericRange.inclusive(x, x, x) + def gr3 = GenericRange(x, x * fromInt(10), x) + def gr4 = GenericRange.inclusive(x, x * fromInt(10), x) + + def check = assert( + gr1.isEmpty && !gr2.isEmpty && + gr3.size == 9 && gr4.size == 10 && + (gr3.toList ::: negated.gr3.toList).sum == num.zero && + !(gr3 contains (x * fromInt(10))) && + (gr4 contains (x * fromInt(10))) + ) + } + + def main(args: Array[String]): Unit = { + implicit val imp1 = Numeric.BigDecimalAsIfIntegral + implicit val imp2 = Numeric.DoubleAsIfIntegral + + val _grs = List[GR[_]]( + GR(BigDecimal(5.0)), + GR(BigInt(5)), + GR(5L), + GR(5.0d), + GR(2.toByte) + ) + val grs = _grs ::: (_grs map (_.negated)) + grs foreach (_.check) + + assert(GenericRange(1, 10, 1) sameElements (1 until 10)) + assert(GenericRange.inclusive(1, 10, 1) sameElements (1 to 10)) + assert(GenericRange.inclusive(1, 100, 3) sameElements (1 to 100 by 3)) + + rangeForeach(1 to 10); + rangeForeach(1 until 10); + rangeForeach(10 to 1 by -1); + rangeForeach(10 until 1 by -1); + rangeForeach(10 to 1 by -3); + rangeForeach(10 until 1 by -3); + } } |