代码之家  ›  专栏  ›  技术社区  ›  olle kullberg

使用RichDouble.to操作获取NumericRange

  •  6
  • olle kullberg  · 技术社区  · 14 年前

    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 ...
    
    3 回复  |  直到 14 年前
        1
  •  8
  •   retronym    14 年前

    这个大的十进制数到底是从哪里来的?从…开始 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)
      }
    

    然后移到 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)
        }
      }
    

    toBD 使用 MathContext 允许四舍五入吗?两害孰轻?我将把那个问题推迟到即席讨论。

        2
  •  7
  •   Randall Schulz    14 年前

    我只是 answered this 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
    

    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)
    
        3
  •  2
  •   olle kullberg    14 年前

    好吧,既然这里什么都没发生,我建议在 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)
      }
    

    我们需要一个解决办法,我不在乎是否有微小的精度损失。