Scala 双精度

有没有一个函数可以截断或圆双精度?在我的代码的一点,我想一个数字像: 1.23456789四舍五入到 1.23

158953 次浏览

你可以使用 scala.math.BigDecimal:

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

还有一些其他的 四舍五入模式,不幸的是,目前没有很好的文件(虽然 它们的 Java 等价物是)。

这是另一个没有大十进制的解决方案

截短:

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

舍入函数也可以这样做,这次使用局部套用:

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)

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

如果你想速战速决,海东的想法是对的。但是 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之间的任何位置进行舍入。

既然还没有人提到 %操作员,那么现在开始吧。它只执行截断,而且不能依赖返回值来避免浮点数不精确,但有时它很方便:

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

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

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。我为 FloatDouble 和隐式显式选项都提供了两个重载函数。注意,在函数重载的情况下,需要显式提及返回类型。

这个怎么样:

 val value = 1.4142135623730951


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


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

可以使用隐式类:

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

对于那些如何感兴趣,这里有一些时间为建议的解决方案..。

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


Truncation
Scala custom Formatter: Elapsed Time: 3

截断是最快的,其次是 BigDecimal。 请记住,这些测试是在普通的 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())
}
}
}


}

如果你关心性能,我不会使用 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的建议进行数学处理。

有点奇怪,但很好,我用的是 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
}

你可以做: Math.round(<double precision value> * 100.0) / 100.0 但是 Math.round 是最快的,但是在小数位数很高(例如 round (1000.0 d,17))或者大整数部分(例如 round (90080070060.1 d,9)的情况下,它会出现严重的故障。

使用大十进制它是有点低效,因为它将值转换为字符串,但更真实: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() 使用你喜欢的舍入模式。

如果你对此感到好奇,想知道更多细节,你可以阅读以下内容: enter image description here

实际上,使用 Scala f插值器 https://docs.scala-lang.org/overviews/core/string-interpolation.html很容易处理

假设我们要四舍五入到小数点后两位:

scala> val sum = 1 + 1/4D + 1/7D + 1/10D + 1/13D
sum: Double = 1.5697802197802198


scala> println(f"$sum%1.2f")
1.57

这些都是很好的答案。为了更好地显示差异,这里只是一个例子。我在工作时把它写在这里的原因是,这些数字不能是半数:

    import org.apache.spark.sql.types._
val values = List(1.2345,2.9998,3.4567,4.0099,5.1231)
val df = values.toDF
df.show()
+------+
| value|
+------+
|1.2345|
|2.9998|
|3.4567|
|4.0099|
|5.1231|
+------+


val df2 = df.withColumn("floor_val", floor(col("value"))).
withColumn("dec_val", col("value").cast(DecimalType(26,2))).
withColumn("floor2", (floor(col("value") * 100.0)/100.0).cast(DecimalType(26,2)))


df2.show()
+------+---------+-------+------+
| value|floor_val|dec_val|floor2|
+------+---------+-------+------+
|1.2345|        1|   1.23|  1.23|
|2.9998|        2|   3.00|  2.99|
|3.4567|        3|   3.46|  3.45|
|4.0099|        4|   4.01|  4.00|
|5.1231|        5|   5.12|  5.12|
+------+---------+-------+------+

floor函数楼层的最大整数小于当前值。默认情况下,DecimalType将启用 HALF_UP模式,而不仅仅是切换到您想要的精度。如果您想在不使用 HALF_UP模式的情况下切割到一定的精度,可以使用上述解决方案(或使用 scala.math.BigDecimal(必须显式定义舍入模式))。

由于问题特别指定了双精度数的四舍五入,这似乎比处理大整数或过多的字符串或数值操作要简单得多。

"%.2f".format(0.714999999999).toDouble

我认为以前的答案是:

  • 完全错误: 例如,使用 math.floor 对负值不起作用。
  • 不必要的复杂。

以下是根据@kaito 的回答提出的一个建议(我现在还不能发表评论) :

def truncateAt(x: Double, p: Int): Double = {
val s = math.pow(10, p)
(x * s).toInt / s
}

ToInt 将为正值和负值工作。