Czy istnieje funkcja, która może obciąć lub zaokrąglić Double? W pewnym momencie mojego kodu chciałbym, aby liczba taka jak: 1.23456789
została zaokrąglona do1.23
Czy istnieje funkcja, która może obciąć lub zaokrąglić Double? W pewnym momencie mojego kodu chciałbym, aby liczba taka jak: 1.23456789
została zaokrąglona do1.23
Odpowiedzi:
Możesz użyć scala.math.BigDecimal
:
BigDecimal(1.23456789).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble
Istnieje wiele innych trybów zaokrąglania , które niestety nie są obecnie dobrze udokumentowane (chociaż ich odpowiedniki w Javie są ).
"%.2f".format(x).toDouble
w takim przypadku. Tylko 2x wolniej i musisz korzystać tylko z biblioteki, którą już znasz.
scala> "%.2f".format(0.714999999999).toDouble
res13: Double = 0.71
ale scala> "%.2f".format(0.715).toDouble
res14: Double = 0.72
.
Oto inne rozwiązanie bez BigDecimals
Ścięty:
(math floor 1.23456789 * 100) / 100
Okrągły:
(math rint 1.23456789 * 100) / 100
Lub dla dowolnego podwójnego n i precyzji p:
def truncateAt(n: Double, p: Int): Double = { val s = math pow (10, p); (math floor n * s) / s }
Podobnie można zrobić z funkcją zaokrąglania, tym razem używając curry:
def roundAt(p: Int)(n: Double): Double = { val s = math pow (10, p); (math round n * s) / s }
który jest bardziej przydatny do ponownego wykorzystania, np. przy zaokrąglaniu kwot pieniędzy można zastosować:
def roundAt2(n: Double) = roundAt(2)(n)
NaN
, prawda?
floor
polega na tym, truncateAt(1.23456789, 8)
że zwróci, 1.23456788
a roundAt(1.23456789, 8)
zwróci prawidłową wartość1.23456789
Ponieważ nikt jeszcze nie wspomniał o %
operatorze, nadchodzi. Wykonuje tylko obcinanie i nie można polegać na zwracanej wartości, aby nie mieć niedokładności zmiennoprzecinkowych, ale czasami jest to przydatne:
scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
26.257391515826225 - 0.057391515826223094 = 26.200000000000003
Co powiesz na :
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
nie testowałem tego za dużo. Ten kod zrobiłby 2 miejsca po przecinku.
Edycja: naprawiono problem wskazany przez @ryryguy. (Dzięki!)
Jeśli chcesz, żeby było szybko, Kaito ma dobry pomysł. math.pow
jest jednak powolny. W przypadku każdego standardowego zastosowania lepiej jest skorzystać z funkcji rekurencyjnej:
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
}
}
Jest to około 10 razy szybsze, jeśli znajdujesz się w zakresie Long
(tj. 18 cyfr), więc możesz zaokrąglić w dowolnym miejscu między 10 ^ 18 a 10 ^ -18.
scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)
==> res12: Double = 0.023514999999999998
. Zamiast tego podziel według znaczenia:math.round(x*100000)/100000.0
p10
funkcji rekurencyjnej wyszukiwaniem tablicy: tablica zwiększy zużycie pamięci o około 200 bajtów, ale prawdopodobnie zapisze kilka iteracji na wywołanie.
Możesz używać niejawnych klas:
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))
}
Dla zainteresowanych kilka razy proponowane rozwiązania ...
Rounding
Java Formatter: Elapsed Time: 105
Scala Formatter: Elapsed Time: 167
BigDecimal Formatter: Elapsed Time: 27
Truncation
Scala custom Formatter: Elapsed Time: 3
Obcięcie jest najszybsze, a po nim następuje BigDecimal. Należy pamiętać, że te testy zostały przeprowadzone z uruchomieniem norma scala, bez użycia narzędzi do testów porównawczych.
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
i połącz je z łańcuchami "." i doubleParts. head
przeanalizuj, aby podwoić.
toString.split(".")
i doubleParts.head/tail
sugestia mogą ucierpieć z powodu dodatkowej alokacji tablicy i konkatenacji ciągów. musiałby jednak przetestować, aby mieć pewność.
Niedawno stanąłem przed podobnym problemem i rozwiązałem go stosując następujące podejście
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)
Użyłem tego problemu SO. Mam kilka przeciążonych funkcji zarówno dla opcji Float \ Double, jak i niejawnych \ jawnych. Zauważ, że w przypadku przeciążonych funkcji musisz wyraźnie wspomnieć o zwracanym typie.
W rzeczywistości jest to bardzo łatwe w obsłudze za pomocą f
interpolatora Scala - https://docs.scala-lang.org/overviews/core/string-interpolation.html
Załóżmy, że chcemy zaokrąglić do 2 miejsc po przecinku:
scala> val sum = 1 + 1/4D + 1/7D + 1/10D + 1/13D
sum: Double = 1.5697802197802198
scala> println(f"$sum%1.2f")
1.57
Nie użyłbym BigDecimal, jeśli zależy ci na wydajności. BigDecimal konwertuje liczby na ciąg, a następnie analizuje je ponownie:
/** 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)
Mam zamiar trzymać się manipulacji matematycznych, jak zasugerował Kaito .
Możesz to zrobić: Math.round(<double precision value> * 100.0) / 100.0
Ale Math.round jest najszybsza, ale źle się psuje w przypadkach narożnych z bardzo dużą liczbą miejsc dziesiętnych (np. Zaokrąglenie (1000.0d, 17)) lub dużą częścią całkowitą (np. Round (90080070060.1d, 9) ).
Użyj Bigdecimal, jest to nieco nieefektywne, ponieważ konwertuje wartości na łańcuchy, ale bardziej przypomina:
BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue()
użyj preferencji trybu zaokrąglania.
Jeśli jesteś ciekawy i chcesz dowiedzieć się więcej szczegółów, dlaczego tak się dzieje, możesz przeczytać to: