webentwicklung-frage-antwort-db.com.de

Scala verdoppelt und Präzision

Gibt es eine Funktion, die ein Double abschneiden oder abrunden kann? An einem Punkt in meinem Code möchte ich, dass eine Zahl wie: 1.23456789 auf 1.23 gerundet wird.

94
richsoni

Sie können scala.math.BigDecimal verwenden:

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 ihre Java-Entsprechungen sind.

118
Travis Brown

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 ein beliebiges doppeltes n und eine genaue p:

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

Ähnliches kann für die Rundungsfunktion gemacht werden, diesmal unter Verwendung von Currying:

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

was wiederverwendbar ist, z.B. Bei der Rundung von Geldbeträgen kann Folgendes verwendet werden:

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

Da noch niemand den %-Operator erwähnt hat, kommt hier. Es wird nur abgeschnitten, und Sie können sich nicht darauf verlassen, dass der Rückgabewert keine Ungenauigkeiten der Gleitkommazahlen hat.

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

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)
8
blue-sky

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

Wenn Sie möchten, dass es schnell geht, hat Kaito die richtige Idee. math.pow ist jedoch langsam. Für jede Standardanwendung steht Ihnen eine rekursive Funktion zur Verfügung:

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 etwa 10x schneller, wenn Sie sich im Bereich von Long (d. H. 18 Ziffern) befinden. Sie können also zwischen 10 ^ 18 und 10 ^ -18 runden.

7
Rex Kerr

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))
}
4
Mitrakov Artem

Vor kurzem war ich mit einem ähnlichen Problem konfrontiert und habe es mit folgendem 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 this SO verwendet. Ich habe ein paar überladene Funktionen für Float\Double und implizite\explizite Optionen. Beachten Sie, dass Sie den Rückgabetyp bei überlasteten Funktionen explizit angeben müssen.

3

Für diejenigen, die daran interessiert sind, hier einige Male 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 

Die Kürzung erfolgt am schnellsten, gefolgt von BigDecimal. Denken Sie daran, dass diese Tests mit der Ausführung von norma scala durchgeführt wurden und keine Benchmarking-Tools verwendet 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())
      }
    }
  }

}
3
cevaris

Ich würde BigDecimal nicht verwenden, wenn Sie Wert auf Leistung legen. BigDecimal konvertiert Zahlen in einen String und analysiert ihn 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.

1
bigonazzi

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

Wenn Sie Bigdecimal verwenden, ist dies etwas ineffizient, da die Werte in Zeichenfolgen konvertiert werden, dies ist jedoch relievanter: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() Verwenden Sie den Rundungsmodus, den Sie bevorzugen.

Wenn Sie neugierig sind und genauer wissen möchten, warum dies passiert, können Sie dies lesen:  enter image description here 

0
frostcs

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
jafed