Scala Doubles, and Precision

ScalaDoubleTruncateRounding

Scala Problem Overview


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

Scala Solutions


Solution 1 - Scala

You can use scala.math.BigDecimal:

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

There are a number of other rounding modes, which unfortunately aren't very well documented at present (although their Java equivalents are).

Solution 2 - Scala

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

Solution 3 - Scala

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

Solution 4 - Scala

How about :

 val value = 1.4142135623730951

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

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

Solution 5 - Scala

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.

Solution 6 - Scala

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

}

Solution 7 - Scala

It's actually very easy to handle using Scala f interpolator - https://docs.scala-lang.org/overviews/core/string-interpolation.html

Suppose we want to round till 2 decimal places:

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

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

Solution 8 - Scala

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

Solution 9 - Scala

Recently, I faced similar problem and I solved it using following approach

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)

I used this SO issue. I have couple of overloaded functions for both Float\Double and implicit\explicit options. Note that, you need to explicitly mention the return type in case of overloaded functions.

Solution 10 - Scala

Those are great answers in this thread. In order to better show the difference, here is just an example. The reason I put it here b/c during my work the numbers are required to be NOT half-up :

    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 function floors to the largest interger less than current value. DecimalType by default will enable HALF_UP mode, not just cut to precision you want. If you want to cut to a certain precision without using HALF_UP mode, you can use above solution instead ( or use scala.math.BigDecimal (where you have to explicitly define rounding modes).

Solution 11 - Scala

I wouldn't use BigDecimal if you care about performance. BigDecimal converts numbers to string and then parses it back again:

  /** 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)

I'm going to stick to math manipulations as Kaito suggested.

Solution 12 - Scala

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
}

Solution 13 - Scala

You can do:Math.round(<double precision value> * 100.0) / 100.0 But Math.round is fastest but it breaks down badly in corner cases with either a very high number of decimal places (e.g. round(1000.0d, 17)) or large integer part (e.g. round(90080070060.1d, 9)).

Use Bigdecimal it is bit inefficient as it converts the values to string but more relieval: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() use your preference of Rounding mode.

If you are curious and want to know more detail why this happens you can read this: enter image description here

Solution 14 - Scala

Since the question specified rounding for doubles specifically, this seems way simpler than dealing with big integer or excessive string or numerical operations.

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

Solution 15 - Scala

I think previous answers are:

  • Plain wrong: using math.floor for example doesn't work for negative values..
  • Unnecessary complicated.

Here is a suggestion based on @kaito's answer (i can't comment yet):

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

toInt will work for positive and negative values.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrichsoniView Question on Stackoverflow
Solution 1 - ScalaTravis BrownView Answer on Stackoverflow
Solution 2 - ScalaKaitoView Answer on Stackoverflow
Solution 3 - ScalaakauppiView Answer on Stackoverflow
Solution 4 - Scalablue-skyView Answer on Stackoverflow
Solution 5 - ScalaRex KerrView Answer on Stackoverflow
Solution 6 - ScalacevarisView Answer on Stackoverflow
Solution 7 - ScalaR SunView Answer on Stackoverflow
Solution 8 - ScalaMitrakov ArtemView Answer on Stackoverflow
Solution 9 - ScalaKhalid SaifullahView Answer on Stackoverflow
Solution 10 - ScalaJacqueline P.View Answer on Stackoverflow
Solution 11 - ScalabigonazziView Answer on Stackoverflow
Solution 12 - ScalasomejhereandthereView Answer on Stackoverflow
Solution 13 - ScalafrostcsView Answer on Stackoverflow
Solution 14 - Scalagutscdav000View Answer on Stackoverflow
Solution 15 - ScalafbetteoView Answer on Stackoverflow