繁体   English   中英

浮动步的标量范围精度

[英]scala range precision for float step

我打算用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)

谁能给我一些关于如何解决这个问题的想法? 谢谢。

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

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

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

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

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)

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

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)

如果我正确地解释了您的问题,问题不在于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)

是的,浮点精度会在这里给您带来麻烦,尤其是在尝试生成范围的最后一个元素时。 .950000000002被截断,因为它大于您指定的.95

尽管它的阅读效果并不理想,但我发现在Scala中表现得更加一致的是,它仅使用整数范围,而只是使用a理解来产生所需的计算结果。

因此可以将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)应该可以工作,但是如果步骤不执行,这并不是严格意义上的均匀地划分起点和终点,但是在那种情况下,您显然很困难。

无论如何,我认为这样做效果更好的原因是,除法引入的不准确性要比取不能精确表示的数(步)的乘积小。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM