Scala倍增精度


117

是否存在可以截断或舍入Double的函数?在代码的某一点上,我想要一个数字:1.23456789四舍五入为1.23


9
看完所有答案后,我想简短的答案是否定的?:)
Marsellus Wallace

4
@Gevorg你让我笑了。我是来自其他使用大量数字的语言的Scala的新手,而我的下巴几乎在阅读此线程时跌落在地。这是编程语言的一种疯狂状态。
ely

Answers:


150

您可以使用scala.math.BigDecimal

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

还有许多其他舍入模式,不幸的是,目前尚无很好的文档记录(尽管Java等效项是)。


5
我很可能会说。任何涉及网格或财务的内容都可能需要取整和性能。
Rex Kerr 2012年

27
我想有些人对笨拙的图书馆的漫长呼唤比简单的数学更容易理解。我建议"%.2f".format(x).toDouble那种情况。仅慢2倍,您只需要使用一个已经知道的库即可。
Rex Kerr 2012年

6
@RexKerr,在这种情况下,您没有舍入..只是被截断了。
何塞·莱亚尔

16
@JoséLeal-嗯? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71但是scala> "%.2f".format(0.715).toDouble res14: Double = 0.72
Rex Kerr

5
@RexKerr我更喜欢您的string.format方式,但是在我的语言环境(芬兰语)中,必须注意将其修复为ROOT语言环境。例如“%.2f” .formatLocal(java.util.Locale.ROOT,x).toDouble。看来,格式是使用','的,因为它是区域设置,而toDouble无法将其引入并抛出NumberFormatException。当然,这是基于代码的运行位置,而不是代码的开发位置。
akauppi 2014年

77

这是没有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(n: Double) = roundAt(2)(n)

8
roundAt2应该是def roundAt2(n:Double)= roundAt(2)(n)否?
C4stor 2014年

这似乎返回不正确的结果NaN,不是吗?
jangorecki

问题floor在于,truncateAt(1.23456789, 8)它将返回,1.23456788同时roundAt(1.23456789, 8)将返回正确的值1.23456789
Todor Kolev

35

由于尚无人提及%操作员,因此来了。它仅进行截断,并且您不能依赖于返回值不具有浮点数错误,但是有时很方便:

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

2
尽管这是我自己的答案,但我们不建议这样做:与@ryryguy在另一个答案的评论中提到的相同的不准确性问题也会影响此处。在Java ROOT语言环境中使用string.format(我将在此处进行评论)。
akauppi 2014年

如果您只需要呈现该值并且永远不要在后续操作中使用它,则这是完美的。谢谢
Alexander Arendar 2014年

3
这是一件有趣的事: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi 2015年

12

怎么样 :

 val value = 1.4142135623730951

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

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

很干净的解决方案。这里是我的截断:((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.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之间的任意位置取整。


3
注意,乘以倒数并不可靠,因为它可能无法可靠地表示为double:scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998。相反,按重要性划分:math.round(x*100000)/100000.0
ryryguy 2013年

p10用数组查找替换递归函数也可能很有用:数组将使内存消耗增加约200个字节,但每次调用可能节省数次迭代。
维·拉姆齐

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

1
请指定此方法仅用于截断,而不能正确舍入。
bobo32 '17

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

}

1
亲爱的scalaCustom不四舍五入,它只是截取
Ravinder PAYAL

嗯,OP不是舍入或截断;...truncate or round a Double
cevaris

但是我认为将舍位函数的速度/执行时间与舍入函数进行比较是不够的。因此,我要求您向读者说明自定义功能仅会截断。并且您提到的截断/自定义函数可以进一步简化。val doubleParts = double。toString.split(“。”)现在doubleParts.tail使用字符串“。” 获取and concat 的前两个字符。并doubleParts. head和解析翻番。
Ravinder Payal '18

1
更新,看起来更好吗?另外,您的建议toString.split(".")doubleParts.head/tail建议可能会受到额外的数组分配以及字符串连接的影响。需要进行测试以确保。
cevaris

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)

我用这个 SO问题。对于Float \ Double和隐含\ explicit选项,我有几个重载函数。请注意,在函数重载的情况下,您需要明确提及返回类型。


此外,您还可以使用@雷克斯-科尔的做法为动力,而不是Math.pow
哈立德·赛义夫拉


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建议的数学操作。


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是最快的,但是在小数位数很高(例如round(1000.0d,17))或较大整数部分(例如round(90080070060.1d,9))的极端情况下,它会严重崩溃。 )。

使用Bigdecimal会有点效率低下,因为它会将值转换为字符串,但更为有效:请 BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() 使用“舍入”模式的首选项。

如果您好奇并想了解更多原因,可以阅读以下内容: 在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.