Scala Doubles, and Precision

2019-01-13 06:48发布

Is there a function that can truncate or round a Double? At one point in my code I would like a number like: 1.23456789 to be rounded to 1.23

10条回答
再贱就再见
2楼-- · 2019-01-13 07:11

A bit strange but nice. I use String and not 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
}
查看更多
老娘就宠你
3楼-- · 2019-01-13 07:11

Edit: fixed the problem that @ryryguy pointed out. (Thanks!)

If you want it to be fast, Kaito has the right idea. math.pow is slow, though. For any standard use you're better off with a recursive function:

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
  }
}

This is about 10x faster if you're within the range of Long (i.e 18 digits), so you can round at anywhere between 10^18 and 10^-18.

查看更多
姐就是有狂的资本
4楼-- · 2019-01-13 07:15

For those how are interested, here are some times for the suggested solutions...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

Truncation is the fastest, followed by BigDecimal. Keep in mind these test were done running norma scala execution, not using any benchmarking tools.

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())
      }
    }
  }

}
查看更多
在下西门庆
5楼-- · 2019-01-13 07:20

You may use implicit classes:

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))
}
查看更多
乱世女痞
6楼-- · 2019-01-13 07:21

Since no-one mentioned the % operator yet, here comes. It only does truncation, and you cannot rely on the return value not to have floating point inaccuracies, but sometimes it's handy:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
查看更多
戒情不戒烟
7楼-- · 2019-01-13 07:26

Here's another solution without BigDecimals

Truncate:

(math floor 1.23456789 * 100) / 100

Round:

(math rint 1.23456789 * 100) / 100

Or for any double n and precision p:

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

Similar can be done for the rounding function, this time using currying:

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

which is more reusable, e.g. when rounding money amounts the following could be used:

def roundAt2(p: Int) = roundAt(2)(p)
查看更多
登录 后发表回答