Scala Doubles und Präzision


117

Gibt es eine Funktion, die ein Double abschneiden oder runden kann? An einer Stelle in meinem Code möchte ich eine Zahl wie: 1.23456789gerundet werden1.23


9
Nachdem ich mir alle Antworten angesehen habe, denke ich, dass die kurze Antwort nein ist? :)
Marsellus Wallace

4
@Gevorg Du hast mich zum Lachen gebracht. Ich bin neu in Scala aus anderen numerikintensiven Sprachen, und mein Kiefer fiel fast auf den Boden, als ich diesen Thread las. Dies ist ein verrückter Zustand für eine Programmiersprache.
Ely

Antworten:


150

Sie können verwenden scala.math.BigDecimal:

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

Es gibt eine Reihe anderer Rundungsmodi , die derzeit leider nicht sehr gut dokumentiert sind (obwohl dies ihre Java-Entsprechungen sind ).


5
Ziemlich wahrscheinlich, würde ich sagen. Alles, was Netze oder Finanzen betrifft, kann eine Rundung und auch eine Leistung erfordern.
Rex Kerr

27
Ich nehme an, es gibt Leute, für die ein langer Anruf in einer klobigen Bibliothek verständlicher ist als einfache Mathematik. Ich würde "%.2f".format(x).toDoublein diesem Fall empfehlen . Nur 2x langsamer und Sie müssen nur eine Bibliothek verwenden, die Sie bereits kennen.
Rex Kerr

6
@RexKerr, Sie runden in diesem Fall nicht .. einfach abschneiden.
José Leal

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

5
@RexKerr Ich bevorzuge Ihre string.format-Methode, aber in Gebietsschemas wie meinem (finnisch) muss darauf geachtet werden, dass das Gebietsschema ROOT korrigiert wird. ZB "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Es scheint, dass das Format aufgrund des Gebietsschemas ',' verwendet, während toDouble es nicht aufnehmen kann und eine NumberFormatException auslöst. Dies ist natürlich basiert auf dem der Code wird ausgeführt , nicht , wo es entwickelt wird .
Akauppi

77

Hier ist eine andere Lösung ohne BigDecimals

Kürzen:

(math floor 1.23456789 * 100) / 100

Runden:

(math rint 1.23456789 * 100) / 100

Oder für jedes doppelte n und jede Genauigkeit p:

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

Ähnliches gilt für die Rundungsfunktion, diesmal mit Currying:

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

Das ist wiederverwendbarer, z. B. wenn Geldbeträge gerundet werden, könnte Folgendes verwendet werden:

def roundAt2(n: Double) = roundAt(2)(n)

8
roundAt2 sollte def roundAt2 (n: Double) = roundAt (2) (n) nein sein?
C4stor

Dies scheint ein falsches Ergebnis zu liefern NaN, nicht wahr?
Jangorecki

Das Problem mit floorist, dass zurückgegeben truncateAt(1.23456789, 8)wird, 1.23456788während roundAt(1.23456789, 8)der korrekte Wert von1.23456789
Todor Kolev

35

Da noch niemand den %Betreiber erwähnt hat, kommt hier. Es wird nur abgeschnitten, und Sie können sich nicht darauf verlassen, dass der Rückgabewert keine Gleitkomma-Ungenauigkeiten aufweist, aber manchmal ist es praktisch:

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

2
Ich würde dies nicht empfehlen, obwohl es meine eigene Antwort ist: Die gleichen Ungenauigkeitsprobleme, die @ryryguy im Kommentar einer anderen Antwort erwähnt hat, wirken sich auch hier aus. Verwenden Sie string.format mit dem Java ROOT-Gebietsschema (ich werde das dort kommentieren).
Akauppi

Dies ist perfekt, wenn Sie den Wert nur rendern müssen und ihn niemals in nachfolgenden Vorgängen verwenden müssen. danke
Alexander Arendar

3
Hier ist etwas Lustiges: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
Kubudi

12

Wie wäre es mit :

 val value = 1.4142135623730951

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

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

ziemlich saubere Lösung. Hier ist meine zum Abschneiden: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoublehabe es aber nicht zu oft getestet. Dieser Code würde 2 Dezimalstellen machen.
Robert

7

Bearbeiten: Das Problem, auf das @ryryguy hingewiesen hat, wurde behoben. (Vielen Dank!)

Wenn Sie möchten, dass es schnell geht, hat Kaito die richtige Idee. math.powist jedoch langsam. Für jede Standardanwendung sind Sie mit einer rekursiven Funktion besser dran:

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

Dies ist ungefähr 10x schneller, wenn Sie sich im Bereich von Long(dh 18 Stellen) befinden, sodass Sie zwischen 10 ^ 18 und 10 ^ -18 runden können.


3
Achtung, das Multiplizieren mit dem Kehrwert funktioniert nicht zuverlässig, da es möglicherweise nicht zuverlässig als Doppel dargestellt werden kann: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Teilen Sie stattdessen durch die Bedeutung:math.round(x*100000)/100000.0
Ryryguy

Es kann auch nützlich sein, die rekursive p10Funktion durch eine Array-Suche zu ersetzen : Das Array erhöht den Speicherverbrauch um etwa 200 Byte, spart jedoch wahrscheinlich mehrere Iterationen pro Aufruf.
Levi Ramsey

4

Sie können implizite Klassen verwenden:

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
Bitte geben Sie an, dass diese Methode nur zum Abschneiden und nicht zum korrekten Runden dient.
Bobo32

4

Für diejenigen, die interessiert sind, hier einige Zeiten für die vorgeschlagenen Lösungen ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

Das Abschneiden ist am schnellsten, gefolgt von BigDecimal. Beachten Sie, dass diese Tests mit der Ausführung von Norma Scala ohne Verwendung von Benchmarking-Tools durchgeführt wurden.

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
Lieber scalaCustom rundet nicht ab, er schneidet nur ab
Ravinder Payal

hmm, OP war nicht spezifisch für das Runden oder Abschneiden; ...truncate or round a Double.
Cevaris

Meiner Meinung nach ist es jedoch unzureichend, die Geschwindigkeit / Ausführungszeit der Abschneidefunktion mit Rundungsfunktionen zu vergleichen. Aus diesem Grund habe ich Sie gebeten, dem Leser klar zu machen, dass die benutzerdefinierte Funktion nur abgeschnitten wird. Die von Ihnen erwähnte Funktion zum Abschneiden / Benutzerdefinieren kann weiter vereinfacht werden. val doubleParts = double. toString.split (".") doubleParts.tailHolen Sie sich jetzt die ersten beiden Zeichen von und concat mit Strings "." und doubleParts. headund analysieren, um zu verdoppeln.
Ravinder Payal

1
aktualisiert, besser aussehen? Auch Ihr Vorschlag toString.split(".")und doubleParts.head/tailVorschlag kann unter einer zusätzlichen Array-Zuweisung und einer Verkettung von Zeichenfolgen leiden. müsste allerdings testen, um sicher zu sein.
Cevaris

3

Vor kurzem hatte ich ein ähnliches Problem und habe es mit dem folgenden Ansatz gelöst

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)

Ich habe dieses SO-Problem verwendet. Ich habe einige überladene Funktionen für Float \ Double und implizite \ explizite Optionen. Beachten Sie, dass Sie bei überladenen Funktionen den Rückgabetyp explizit angeben müssen.


Sie können auch @ rex-kerrs Ansatz für die Leistung anstelle von Math.pow
Khalid Saifullah


1

Ich würde BigDecimal nicht verwenden, wenn Sie Wert auf Leistung legen. BigDecimal konvertiert Zahlen in Zeichenfolgen und analysiert sie dann erneut:

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

Ich werde mich an mathematische Manipulationen halten, wie Kaito vorgeschlagen hat.


0

Ein bisschen seltsam, aber nett. Ich benutze String und nicht 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

Sie können Math.round(<double precision value> * 100.0) / 100.0 Folgendes tun: Aber Math.round ist am schnellsten, bricht jedoch in Eckfällen mit einer sehr hohen Anzahl von Dezimalstellen (z. B. rund (1000.0d, 17)) oder einem großen ganzzahligen Teil (z. B. rund (90080070060.1d, 9) schlecht zusammen. ).

Verwenden Sie Bigdecimal, es ist etwas ineffizient, da es die Werte in Zeichenfolgen konvertiert, aber mehr Erleichterung: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() Verwenden Sie den Rundungsmodus.

Wenn Sie neugierig sind und mehr darüber erfahren möchten, warum dies geschieht, können Sie Folgendes lesen: Geben Sie hier die Bildbeschreibung ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.