简体   繁体   English

浮动步的标量范围精度

[英]scala range precision for float step

I intend to generate an array from 0.05 to 0.95 with step 0.05, just like 0.05, 0.1, 0.15, 0.2, ... . 我打算用0.05步从0.05到0.95生成一个数组,就像0.05, 0.1, 0.15, 0.2, ... But the following code has some precision problems: 但是以下代码存在一些精度问题:

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)

Could anyone give me some idea on how to solve this problem? 谁能给我一些关于如何解决这个问题的想法? Thanks. 谢谢。

If you need precise decimal calculations, the actually reliable way is to use BigDecimal : 如果需要精确的十进制计算,则实际可靠的方法是使用BigDecimal

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

It's a lot slower, so not acceptable in some contexts, but that's the reality of working with decimals on modern computers. 它慢很多,所以在某些情况下是不可接受的,但这是在现代计算机上使用小数的现实。

You could use rounding to get job done: 您可以使用舍入来完成工作:

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)

a more logical explanation could be this 一个更合乎逻辑的解释可能是

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)

If I interpret your question correctly, the problem isn't that 0.15 is notated as 0.15000000000000002, but rather that the max value (0.95) is not actually produced. 如果我正确地解释了您的问题,问题不在于0.15被标记为0.15000000000000002,而是实际上未产生最大值(0.95)。 This is an annoying interaction between range and floating point precision, and there's no easy way to get around it. 这是范围和浮点精度之间令人讨厌的相互作用,没有轻松的方法可以解决。 I've written one solution below, though it's pretty hacky. 我在下面写了一个解决方案,尽管它很hacky。 It's a little less efficient than the default numeric range, but still indexes elements in constant time. 它的效率比默认数值范围低一点,但仍会在恒定时间内索引元素。

REPL: 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)

Yeah the floating point precision bites you here especially with when trying to generate the last element of the range. 是的,浮点精度会在这里给您带来麻烦,尤其是在尝试生成范围的最后一个元素时。 .950000000002 gets cut off because it's greater than the .95 you specified. .950000000002被截断,因为它大于您指定的.95

While it doesn't read as nicely, what I've found to behave much more consistently in Scala is to use integer ranges exclusively, and just use a for comprehension to yield the computed results you want. 尽管它的阅读效果并不理想,但我发现在Scala中表现得更加一致的是,它仅使用整数范围,而只是使用a理解来产生所需的计算结果。

So something like 0.05 to 0.95 by 0.05 can be transformed into for (i <- 1 to 19) yield i.toFloat / 20 . 因此可以将0.05 to 0.95 by 0.05转换for (i <- 1 to 19) yield i.toFloat / 20

In general something like start to end by step transforms into for (i <- (start / step) to (end / step)) yield i.toFloat / (1/step) should work but it's not strictly general if step doesn't divide both start and end evenly but in that case you're clearly just being difficult. 通常, start to end by step转换为for (i <- (start / step) to (end / step)) yield i.toFloat / (1/step)应该可以工作,但是如果步骤不执行,这并不是严格意义上的均匀地划分起点和终点,但是在那种情况下,您显然很困难。

In any case, I think the reason this works much better is that dividing introduces less imprecision than taking a product of a number (the step) that can't be represented exactly. 无论如何,我认为这样做效果更好的原因是,除法引入的不准确性要比取不能精确表示的数(步)的乘积小。

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

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