web-dev-qa-db-fra.com

Scala double et précision

Existe-t-il une fonction qui peut tronquer ou arrondir un double? À un moment donné dans mon code, j'aimerais qu'un chiffre comme 1.23456789 soit arrondi à 1.23

94
richsoni

Vous pouvez utiliser scala.math.BigDecimal :

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

Il existe un certain nombre d'autres modes d'arrondi , qui malheureusement ne sont pas très bien documentés à l'heure actuelle (bien que leurs équivalents Java soient ).

118
Travis Brown

Voici une autre solution sans BigDecimals

Tronquer:

(math floor 1.23456789 * 100) / 100

Rond: 

(math rint 1.23456789 * 100) / 100

Ou pour tout double n et précision p:

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

Une opération similaire peut être réalisée pour la fonction d'arrondi, cette fois en utilisant le currying:

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

qui est plus réutilisable, par exemple en arrondissant les montants, on peut utiliser:

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

Puisque personne n'a encore mentionné l'opérateur %, voici. Il ne fait que la troncature, et vous ne pouvez pas compter sur la valeur de retour pour ne pas avoir d'inexactitudes en virgule flottante, mais parfois c'est pratique:

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

Que diriez-vous : 

 val value = 1.4142135623730951

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

//4 decimal places
println((value * 10000).round / 10000.toDouble)
8
blue-sky

Edit: correction du problème signalé par @ryryguy. (Merci!)

Si vous voulez que ce soit rapide, Kaito a la bonne idée. math.pow est lent, cependant. Pour toute utilisation standard, il vaut mieux utiliser une fonction récursive:

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

Ceci est environ 10 fois plus rapide si vous êtes dans la plage de Long (18 chiffres), vous pouvez donc arrondir n'importe où entre 10 ^ 18 et 10 ^ -18.

7
Rex Kerr

Vous pouvez utiliser des classes implicites:

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
Mitrakov Artem

Récemment, j'ai rencontré un problème similaire et je l'ai résolu en utilisant l'approche suivante

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)

J'ai utilisé this SO issue. J'ai quelques fonctions surchargées pour les options Float\Double et implicite\explicit. Notez que vous devez mentionner explicitement le type de retour en cas de fonctions surchargées.

3
Khalid Saifullah

Pour ceux qui sont intéressés, voici quelques moments pour les solutions suggérées ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

La troncature est la plus rapide, suivie de BigDecimal. N'oubliez pas que ces tests ont été effectués avec l'exécution de norma scala, sans utiliser aucun outil d'analyse comparative. 

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
cevaris

Je n'utiliserais pas BigDecimal si vous vous souciez de la performance. BigDecimal convertit les nombres en chaîne, puis les analyse à nouveau:

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

Je vais m'en tenir aux manipulations mathématiques comme Kaito a suggéré.

1
bigonazzi

Vous pouvez faire: Math.round(<double precision value> * 100.0) / 100.0 Mais Math.round est le plus rapide, mais il tombe mal en cas de virages avec un très grand nombre de décimales (par exemple, rond (1000.0d, 17)) ou un grand entier (par exemple, rond (90080070060.1d, )).

Utilisez Bigdecimal car il est peu efficace car il convertit les valeurs en chaîne, mais plus relâche: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue(), utilisez votre préférence pour le mode Arrondi.

Si vous êtes curieux et que vous voulez en savoir plus sur les raisons pour lesquelles cela se produit, vous pouvez lire ceci:  enter image description here 

0
frostcs

Un peu étrange mais sympa. J'utilise String et non 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
jafed