Using RichDouble.to operation to get a NumericRange
I am a bit confused by this ArithmeticException. I have tried this on Scala 2.8.0.RC6 and RC7.
scala> 7.12 to(8, 0.2)
res0: scala.collection.immutable.NumericRange[Double] = NumericRange(7.12, 7.32, 7.52, 7.72, 7.92)
scala> 7.12 to(8, 0.5)
res2: scala.collection.immutable.NumericRange[Double] = NumericRange(7.12, 7.62)
scala> 7.12 to(8, 0.3)
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1525)
at java.math.BigDecimal.divide(BigDecimal.java:1558)
at scala.math.BigDecimal.$div(BigDecimal.scala:228)
at scala.math.Numeric$BigDecimalAsIfIntegral$class.quot(Numeric.scala:156)
at scala.math.Numeric$BigDecimalAsIfIntegral$.quot(Numeric.scala:163)
at scala.math.Numeric$BigDecimalAsIfIntegral$.quot(Numeric.scala:163)
at scala.math.Integral$IntegralOps.$div$p开发者_StackOverflow社区ercent(Integral.scala:23)
at scala.collection.immutable.NumericRange.genericLength(NumericRange.scala:104)
at scala.collection.immutable.NumericRange.<init>(NumericRange.scala:63)
at scala.collection.immutable.NumericRange$Inclusive.<init>(NumericRange.scala:209)
at ...
Where exactly does this BigDecimal come from? Start with Range.scala
// 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 {
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)
}
And move to NumericRange.scala:
// 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
//
// NumericRange[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
//
// NumericRange[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]): NumericRange[A] = {
val self = this
// XXX This may be incomplete.
new NumericRange[A](fm(start), fm(end), fm(step), isInclusive) {
def copy(start: A, end: A, step: A): NumericRange[A] =
if (isInclusive) NumericRange.inclusive(start, end, step)
else NumericRange(start, end, step)
private val underlyingRange: NumericRange[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(el: A) = underlyingRange exists (x => fm(x) == el)
}
}
Would the sky fall if toBD
used a MathContext
that allowed rounding? Which is the lesser of two evils? I'll defer that question to @extempore.
I just answered this on Scala Forum:
scala> import java.math.{ MathContext => MC, RoundingMode => RM }
import java.math.{MathContext=>MC, RoundingMode=>RM}
scala> val mc1 = new MC(6, RM.HALF_DOWN)
mc1: java.math.MathContext = precision=6 roundingMode=HALF_DOWN
scala> val a1 = BigDecimal(1, mc1)
a1: scala.math.BigDecimal = 1
scala> val b1 = BigDecimal(3, mc1)
b1: scala.math.BigDecimal = 3
scala> a1 / b1
res10: scala.math.BigDecimal = 0.333333
scala> val a2 = BigDecimal(1, MC.DECIMAL128)
a2: scala.math.BigDecimal = 1
scala> val b2 = BigDecimal(3, MC.DECIMAL128)
b2: scala.math.BigDecimal = 3
scala> a2 / b2
res11: scala.math.BigDecimal = 0.3333333333333333333333333333333333
I'm not sure, however, this is the result you want:
scala> def BD128(d: Double): BigDecimal = BigDecimal(d, MC.DECIMAL128)
BD128: (d: Double)BigDecimal
scala> BD128(7.12) to(BD128(8), BD128(0.3))
res10: scala.collection.immutable.NumericRange.Inclusive[BigDecimal] = NumericRange(7.12, 7.42, 7.72)
Ok, since nothing happened here i propose the following change in collection.immutable.Range
:
object Double {
implicit val bigDecAsIntegral = scala.Numeric.BigDecimalAsIfIntegral
implicit val doubleAsIntegral = scala.Numeric.DoubleAsIfIntegral
def toBD(x: Double): BigDecimal = {
// Let's round this Double to prevent
// error caused by /% in NumericRange
scala.BigDecimal(x, MC.DECIMAL128)
}
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)
}
We need a fix for this, and I don't care if some tiny precision is lost.
精彩评论