Czy istnieje funkcja, która może obciąć lub zaokrąglić Double? W pewnym momencie mojego kodu chciałbym, aby liczba taka jak: 1.23456789została zaokrąglona do1.23
Po przejrzeniu wszystkich odpowiedzi myślę, że krótka odpowiedź brzmi: nie? :)
Marsellus Wallace
4
@Gevorg Rozśmieszyłeś mnie. Jestem nowy w Scali z innych języków z licznymi liczbami i moja szczęka prawie uderzyła o podłogę czytając ten wątek. To jest szalony stan rzeczy dla języka programowania.
Powiedziałbym, że całkiem prawdopodobne. Wszystko, co dotyczy siatek lub finansów, może wymagać zaokrąglenia, a także wydajności.
Rex Kerr
28
Przypuszczam, że są ludzie, dla których długie wywołanie niezgrabnej biblioteki jest bardziej zrozumiałe niż prosta matematyka. Poleciłbym "%.2f".format(x).toDoublew takim przypadku. Tylko 2x wolniej i musisz korzystać tylko z biblioteki, którą już znasz.
Rex Kerr
6
@RexKerr, w tym przypadku nie zaokrąglasz ... po prostu skracasz.
@RexKerr Wolę twój string.format sposób, ale w ustawieniach regionalnych sa moje (fińskie), należy zachować ostrożność, aby naprawić ustawienie regionalne ROOT. Np. „% .2f” .formatLocal (java.util.Locale.ROOT, x) .toDouble. Wygląda na to, że format używa „,” ze względu na ustawienia regionalne, podczas gdy toDouble nie jest w stanie go przyjąć i zgłasza wyjątek NumberFormatException. To oczywiście zależy od tego, gdzie jest uruchamiany twój kod , a nie gdzie jest rozwijany.
akauppi
77
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ć:
wydaje się, że zwraca nieprawidłowy wynik dla NaN, prawda?
jangorecki
Problem floorpolega na tym, truncateAt(1.23456789, 8)że zwróci, 1.23456788a roundAt(1.23456789, 8)zwróci prawidłową wartość1.23456789
Todor Kolev
35
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:
Nie polecałbym tego, chociaż to moja własna odpowiedź: te same problemy z niedokładnością, o których wspomniał @ryryguy w komentarzu innej odpowiedzi, mają również wpływ tutaj. Użyj string.format z ustawieniami regionalnymi Java ROOT (skomentuję to tam).
akauppi
jest to idealne rozwiązanie, jeśli potrzebujesz tylko wyrenderować wartość i nigdy nie używać jej w kolejnych operacjach. dzięki
Alexander Arendar
3
tu jest coś zabawnego: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi
12
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)
całkiem czyste rozwiązanie. Tutaj jest moje do obcinania: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoublenie testowałem tego za dużo. Ten kod zrobiłby 2 miejsca po przecinku.
robert
7
Edycja: naprawiono problem wskazany przez @ryryguy. (Dzięki!)
Jeśli chcesz, żeby było szybko, Kaito ma dobry pomysł. math.powjest 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.
Uważaj, mnożenie przez odwrotność nie działa niezawodnie, ponieważ może nie być wiarygodnie reprezentowane jako podwójne: 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
ryryguy
Przydatne może być również zastąpienie p10funkcji rekurencyjnej wyszukiwaniem tablicy: tablica zwiększy zużycie pamięci o około 200 bajtów, ale prawdopodobnie zapisze kilka iteracji na wywołanie.
Levi Ramsey
4
Możesz używać niejawnych klas:
import scala.math._
objectExtNumberextendsApp{implicitclassExtendedDouble(n:Double){def rounded(x:Int)={val w = pow(10, x)(n * w).toLong.toDouble / w
}}// usageval a =1.23456789
println(a.rounded(2))}
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.
objectTestFormatters{val r = scala.util.Randomdef 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 =2val 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())}}}}
Drogi Scala, Custom nie kończy się, to tylko skracanie
Ravinder Payal
hmm, OP nie był specyficzny dla zaokrąglania lub obcięcia; ...truncate or round a Double.
cevaris
Jednak moim zdaniem porównywanie szybkości / czasu wykonywania funkcji obcinania z funkcjami zaokrąglającymi jest niewystarczające. Dlatego poprosiłem cię o wyjaśnienie czytelnikowi, że funkcja niestandardowa jest tylko obcięta. Wspomniana przez Ciebie funkcja obcinania / niestandardowa może zostać jeszcze bardziej uproszczona. val doubleParts = double. toString.split (".") Teraz pobierz pierwsze dwa znaki doubleParts.taili połącz je z łańcuchami "." i doubleParts. headprzeanalizuj, aby podwoić.
Ravinder Payal
1
zaktualizowane, lepiej wyglądać? również twoja sugestia toString.split(".")i doubleParts.head/tailsugestia mogą ucierpieć z powodu dodatkowej alokacji tablicy i konkatenacji ciągów. musiałby jednak przetestować, aby mieć pewność.
cevaris
3
Niedawno stanąłem przed podobnym problemem i rozwiązałem go stosując następujące podejście
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.
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=newBigDecimal(newBigDec(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:
Odpowiedzi:
Możesz użyć
scala.math.BigDecimal
:Istnieje wiele innych trybów zaokrąglania , które niestety nie są obecnie dobrze udokumentowane (chociaż ich odpowiedniki w Javie są ).
źródło
"%.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
alescala> "%.2f".format(0.715).toDouble
res14: Double = 0.72
.Oto inne rozwiązanie bez BigDecimals
Ścięty:
Okrągły:
Lub dla dowolnego podwójnego n i precyzji p:
Podobnie można zrobić z funkcją zaokrąglania, tym razem używając curry:
który jest bardziej przydatny do ponownego wykorzystania, np. przy zaokrąglaniu kwot pieniędzy można zastosować:
źródło
NaN
, prawda?floor
polega na tym,truncateAt(1.23456789, 8)
że zwróci,1.23456788
aroundAt(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:źródło
26.257391515826225 - 0.057391515826223094 = 26.200000000000003
Co powiesz na :
źródło
((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: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.źródło
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:
źródło
Dla zainteresowanych kilka razy proponowane rozwiązania ...
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.
źródło
...truncate or round a Double
.doubleParts.tail
i połącz je z łańcuchami "." idoubleParts. head
przeanalizuj, aby podwoić.toString.split(".")
idoubleParts.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
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.
źródło
W rzeczywistości jest to bardzo łatwe w obsłudze za pomocą
f
interpolatora Scala - https://docs.scala-lang.org/overviews/core/string-interpolation.htmlZałóżmy, że chcemy zaokrąglić do 2 miejsc po przecinku:
źródło
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:
Mam zamiar trzymać się manipulacji matematycznych, jak zasugerował Kaito .
źródło
Trochę dziwne, ale miłe. Używam String, a nie BigDecimal
źródło
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:
źródło