Gibt es eine Funktion, die ein Double abschneiden oder runden kann? An einer Stelle in meinem Code möchte ich eine Zahl wie: 1.23456789
gerundet werden1.23
Gibt es eine Funktion, die ein Double abschneiden oder runden kann? An einer Stelle in meinem Code möchte ich eine Zahl wie: 1.23456789
gerundet werden1.23
Antworten:
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 ).
"%.2f".format(x).toDouble
in diesem Fall empfehlen . Nur 2x langsamer und Sie müssen nur eine Bibliothek verwenden, die Sie bereits kennen.
scala> "%.2f".format(0.714999999999).toDouble
res13: Double = 0.71
aber scala> "%.2f".format(0.715).toDouble
res14: Double = 0.72
.
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)
NaN
, nicht wahr?
floor
ist, dass zurückgegeben truncateAt(1.23456789, 8)
wird, 1.23456788
während roundAt(1.23456789, 8)
der korrekte Wert von1.23456789
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
26.257391515826225 - 0.057391515826223094 = 26.200000000000003
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)
((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDouble
habe es aber nicht zu oft getestet. Dieser Code würde 2 Dezimalstellen machen.
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.pow
ist 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.
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
p10
Funktion durch eine Array-Suche zu ersetzen : Das Array erhöht den Speicherverbrauch um etwa 200 Byte, spart jedoch wahrscheinlich mehrere Iterationen pro Aufruf.
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))
}
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())
}
}
}
}
...truncate or round a Double
.
doubleParts.tail
Holen Sie sich jetzt die ersten beiden Zeichen von und concat mit Strings "." und doubleParts. head
und analysieren, um zu verdoppeln.
toString.split(".")
und doubleParts.head/tail
Vorschlag kann unter einer zusätzlichen Array-Zuweisung und einer Verkettung von Zeichenfolgen leiden. müsste allerdings testen, um sicher zu sein.
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.
Die Verwendung des Scala- f
Interpolators ist sehr einfach - https://docs.scala-lang.org/overviews/core/string-interpolation.html
Angenommen, wir möchten auf 2 Dezimalstellen runden:
scala> val sum = 1 + 1/4D + 1/7D + 1/10D + 1/13D
sum: Double = 1.5697802197802198
scala> println(f"$sum%1.2f")
1.57
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.
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: