Scala Doubles和Precision

问题描述 投票:101回答:11

是否有可以截断或舍入Double的函数?在我的代码中的某一点,我想要一个像:1.23456789四舍五入到1.23

scala double truncate rounding
11个回答
128
投票

你可以使用scala.math.BigDecimal

BigDecimal(1.23456789).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

还有许多其他的rounding modes,遗憾的是目前还没有很好的记录(尽管their Java equivalents are)。


0
投票

有点奇怪,但很好。我使用String而不是BigDecimal

def round(x: Double)(p: Int): Double = {
    var A = x.toString().split('.')
    (A(0) + "." + A(1).substring(0, if (p > A(1).length()) A(1).length() else p)).toDouble
}

0
投票

你可以这样做:Math.round(<double precision value> * 100.0) / 100.0但是Math.round是最快的,但它在极小的位数(例如圆形(1000.0d,17))或大整数部分(例如圆形(90080070060.1d,9))的极端情况下严重崩溃))。

使用Bigdecimal它有点效率低,因为它将值转换为字符串但更多的是relieval:BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue()使用您的舍入模式的偏好。

如果你很好奇并想知道为什么会发生这种情况,你可以阅读:enter image description here


70
投票

这是没有BigDecimals的另一种解决方案

截短:

(math floor 1.23456789 * 100) / 100

回合:

(math rint 1.23456789 * 100) / 100

或者对于任何双n和精度p:

def truncateAt(n: Double, p: Int): Double = { val s = math pow (10, p); (math floor n * s) / s }

对于舍入函数可以进行类似的操作,这次使用currying:

def roundAt(p: Int)(n: Double): Double = { val s = math pow (10, p); (math round n * s) / s }

哪个更可重复使用,例如在舍入金额时,可以使用以下内容:

def roundAt2(p: Int) = roundAt(2)(p)

31
投票

由于没有人提到%运营商,所以来了。它只会截断,你不能依赖返回值不会有浮点不准确,但有时它很方便:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23

8
投票

怎么样 :

 val value = 1.4142135623730951

//3 decimal places
println((value * 1000).round / 1000.toDouble)

//4 decimal places
println((value * 10000).round / 10000.toDouble)

7
投票

编辑:解决了@ryryguy指出的问题。 (谢谢!)

如果你想要它快,Kaito有正确的想法。但是,math.pow很慢。对于任何标准用途,您最好使用递归函数:

def trunc(x: Double, n: Int) = {
  def p10(n: Int, pow: Long = 10): Long = if (n==0) pow else p10(n-1,pow*10)
  if (n < 0) {
    val m = p10(-n).toDouble
    math.round(x/m) * m
  }
  else {
    val m = p10(n).toDouble
    math.round(x*m) / m
  }
}

如果你在Long(即18位数)的范围内,这大约快10倍,所以你可以在10 ^ 18到10 ^ -18之间的任何地方进行舍入。


4
投票

您可以使用隐式类:

import scala.math._

object ExtNumber extends App {
  implicit class ExtendedDouble(n: Double) {
    def rounded(x: Int) = {
      val w = pow(10, x)
      (n * w).toLong.toDouble / w
    }
  }

  // usage
  val a = 1.23456789
  println(a.rounded(2))
}

4
投票

对于那些感兴趣的人,这里有一些建议的解决方案......

Rounding
Java Formatter: Elapsed Time: 105
Scala Formatter: Elapsed Time: 167
BigDecimal Formatter: Elapsed Time: 27

Truncation
Scala custom Formatter: Elapsed Time: 3 

截断是最快的,其次是BigDecimal。请记住,这些测试是在运行norma scala执行时完成的,而不是使用任何基准测试工具。

object TestFormatters {

  val r = scala.util.Random

  def textFormatter(x: Double) = new java.text.DecimalFormat("0.##").format(x)

  def scalaFormatter(x: Double) = "$pi%1.2f".format(x)

  def bigDecimalFormatter(x: Double) = BigDecimal(x).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

  def scalaCustom(x: Double) = {
    val roundBy = 2
    val w = math.pow(10, roundBy)
    (x * w).toLong.toDouble / w
  }

  def timed(f: => Unit) = {
    val start = System.currentTimeMillis()
    f
    val end = System.currentTimeMillis()
    println("Elapsed Time: " + (end - start))
  }

  def main(args: Array[String]): Unit = {

    print("Java Formatter: ")
    val iters = 10000
    timed {
      (0 until iters) foreach { _ =>
        textFormatter(r.nextDouble())
      }
    }

    print("Scala Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        scalaFormatter(r.nextDouble())
      }
    }

    print("BigDecimal Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        bigDecimalFormatter(r.nextDouble())
      }
    }

    print("Scala custom Formatter (truncation): ")
    timed {
      (0 until iters) foreach { _ =>
        scalaCustom(r.nextDouble())
      }
    }
  }

}

3
投票

最近,我遇到了类似的问题,我使用以下方法解决了它

def round(value: Either[Double, Float], places: Int) = {
  if (places < 0) 0
  else {
    val factor = Math.pow(10, places)
    value match {
      case Left(d) => (Math.round(d * factor) / factor)
      case Right(f) => (Math.round(f * factor) / factor)
    }
  }
}

def round(value: Double): Double = round(Left(value), 0)
def round(value: Double, places: Int): Double = round(Left(value), places)
def round(value: Float): Double = round(Right(value), 0)
def round(value: Float, places: Int): Double = round(Right(value), places)

我使用this SO问题。我有两个Float \ Double和implicit \ explicit选项的重载函数。请注意,在重载函数的情况下,您需要明确提及返回类型。


1
投票

如果你关心性能,我不会使用BigDecimal。 BigDecimal将数字转换为字符串,然后再次将其解析:

  /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`, rounding if necessary. */
  def decimal(d: Double, mc: MathContext): BigDecimal = new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc)

我将坚持数学操纵,正如Kaito所建议的那样。

© www.soinside.com 2019 - 2024. All rights reserved.