Using RichDouble.to to get NumericRange

I am a little confused by this exception of ArithmeticException. I 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$percent(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 ... 
+6
scala
source share
3 answers

Where 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 go 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 not fall if toBD used a MathContext that allowed rounding? What is the lesser of two evils? I will postpone this question at @extempore.

+7
source share

I just answered it at the 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 am 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) 
+7
source share

Well, since nothing happened here, I suggest the following change to collection.immutable.Range :

  object Double { implicit val bigDecAsIntegral = scala.Numeric.BigDecimalAsIfIntegral implicit val doubleAsIntegral = scala.Numeric.DoubleAsIfIntegral def toBD(x: Double): BigDecimal = { // Let 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 to fix this, and I don’t care if any slight accuracy is lost.

+2
source share

All Articles