浮动步骤的scala范围精度

时间:2016-11-15 10:24:41

标签: scala

我打算用步骤0.05生成0.05到0.95的数组,就像0.05, 0.1, 0.15, 0.2, ...一样。但是下面的代码有一些精度问题:

scala> 0.05 to 0.95 by 0.05
res11: scala.collection.immutable.NumericRange[Double] = NumericRange(0.05, 0.1, 0.15000000000000002, 0.2, 0.25, 0.3, 0.35, 0.39999999999999997, 0.44999999999999996, 0.49999999999999994, 0.5499999999999999, 0.6, 0.65, 0.7000000000000001, 0.7500000000000001, 0.8000000000000002, 0.8500000000000002, 0.9000000000000002)

有谁能告诉我如何解决这个问题?感谢。

5 个答案:

答案 0 :(得分:5)

如果您需要精确的十进制计算,实际可靠的方法是使用BigDecimal

BigDecimal("0.05") to BigDecimal("0.95") by BigDecimal("0.05")

它慢得多,所以在某些情况下不可接受,但这就是在现代计算机上使用小数的现实。

答案 1 :(得分:0)

您可以使用舍入来完成工作:

scala> (0.05 to 0.95 by 0.05) map ( x=> "%.2f".formatLocal(java.util.Locale.ROOT,x).toDouble)
res0: scala.collection.immutable.IndexedSeq[Double] = Vector(0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9)

答案 2 :(得分:0)

更合乎逻辑的解释可能是这个

for(i <-  (0.05 to 0.96 by 0.05)) yield "%.2f".format(i)
res36: scala.collection.immutable.IndexedSeq[String] = Vector(0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95)

答案 3 :(得分:0)

如果我正确地解释了您的问题,问题不是0.15标记为0.15000000000000002,而是实际上没有生成最大值(0.95)。这是范围和浮点精度之间令人讨厌的相互作用,并且没有简单的方法可以绕过它。我在下面写了一个解决方案,虽然它非常hacky。它的效率低于默认数值范围,但仍然会在恒定时间内对元素进行索引。

REPL:

scala> class PreciseDoubleRange(start: Double, end: Double, by: Double, precision: Int = 4) extends IndexedSeq[Double] {
  val precisionScalar = math.pow(10, precision).toLong
  val steps = ((end - start) / by + 1.0 / precisionScalar).toInt

  def length: Int = steps + 1

  def apply(idx: Int): Double = 
    math.round((start + (idx * by)) * precisionScalar).toDouble / precisionScalar
}
defined class PreciseDoubleRange

scala> new PreciseDoubleRange(0.05, 0.95, 0.05)
res11: PreciseDoubleRange = (0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 
  0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95)

答案 4 :(得分:0)

是的,浮点精度会让你感到困惑,尤其是在尝试生成范围的最后一个元素时。 .950000000002会被切断,因为它大于您指定的.95

虽然读起来不太好,但我发现在Scala中表现得更加一致的是仅使用整数范围,只需使用for comprehension就可以得到你想要的计算结果。

因此可以转换为0.05 to 0.95 by 0.05之类的东西 for (i <- 1 to 19) yield i.toFloat / 20

一般情况下start to end by step转换为for (i <- (start / step) to (end / step)) yield i.toFloat / (1/step)之类的内容应该有效,但如果步骤不能均匀地划分开始和结束,那么它并不严格一般,但在这种情况下,您显然很困难。< / p>

在任何情况下,我认为其效果更好的原因在于划分引入的不精确性比采用无法准确表示的数字(步骤)的产品更少。