Ganda Scala, dan Presisi

118

Apakah ada fungsi yang dapat memotong atau membulatkan Double? Pada satu titik dalam kode saya, saya ingin angka seperti: 1.23456789dibulatkan menjadi1.23

richsoni
sumber
9
Setelah melihat semua jawaban, saya rasa jawaban singkatnya adalah tidak? :)
Marsellus Wallace
4
@Gevorg Anda membuat saya tertawa. Saya baru mengenal Scala dari bahasa numerik-berat lainnya, dan rahang saya hampir menyentuh lantai saat membaca utas ini. Ini adalah urusan yang gila untuk bahasa pemrograman.
ely

Jawaban:

150

Anda dapat menggunakan scala.math.BigDecimal:

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

Ada sejumlah mode pembulatan lainnya , yang sayangnya tidak terdokumentasi dengan baik saat ini (meskipun padanan Java mereka ).

Travis Brown
sumber
5
Sangat mungkin, menurutku. Apa pun yang melibatkan jaringan atau keuangan dapat memerlukan pembulatan dan juga kinerja.
Rex Kerr
28
Saya kira ada orang yang panggilan lama ke perpustakaan kikuk lebih bisa dipahami daripada matematika sederhana. Saya akan merekomendasikan "%.2f".format(x).toDoubledalam kasus itu. Hanya 2x lebih lambat, dan Anda hanya perlu menggunakan perpustakaan yang sudah Anda kenal.
Rex Kerr
6
@RexKerr, Anda tidak melakukan pembulatan dalam kasus ini .. hanya memotong.
José Leal
16
@ JoséLeal - Hah? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71tapi scala> "%.2f".format(0.715).toDouble res14: Double = 0.72.
Rex Kerr
5
@RexKerr Saya lebih suka cara string.format Anda, tetapi di lokal sa saya (Finlandia), hati-hati harus dilakukan untuk memperbaiki ke lokal ROOT. Misalnya "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Tampaknya, format menggunakan ',' karena lokal sedangkan toDouble tidak dapat menerimanya dan melontarkan NumberFormatException. Ini tentu saja didasarkan pada tempat kode Anda dijalankan , bukan di mana kode itu dikembangkan.
akauppi
77

Inilah solusi lain tanpa BigDecimals

Memotong:

(math floor 1.23456789 * 100) / 100

Bulat:

(math rint 1.23456789 * 100) / 100

Atau untuk p ganda dan presisi:

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

Hal serupa dapat dilakukan untuk fungsi pembulatan, kali ini menggunakan kari:

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

mana yang lebih dapat digunakan kembali, misalnya ketika pembulatan jumlah uang berikut ini dapat digunakan:

def roundAt2(n: Double) = roundAt(2)(n)
Kaito
sumber
8
roundAt2 harus def roundAt2 (n: Double) = roundAt (2) (n) no?
C4stor
ini sepertinya mengembalikan hasil yang salah NaN, bukan?
jangorecki
Masalahnya flooradalah yang truncateAt(1.23456789, 8)akan kembali 1.23456788sementara roundAt(1.23456789, 8)akan mengembalikan nilai yang benar dari1.23456789
Todor Kolev
35

Karena belum ada yang menyebutkan %operatornya, ini dia. Ini hanya melakukan pemotongan, dan Anda tidak dapat mengandalkan nilai pengembalian untuk tidak memiliki ketidakakuratan floating point, tetapi terkadang berguna:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
akauppi
sumber
2
Tidak akan merekomendasikan ini meskipun itu jawaban saya sendiri: masalah ketidakakuratan yang sama seperti yang disebutkan oleh @ryryguy dalam komentar jawaban lain juga memengaruhi di sini. Gunakan string.format dengan lokal ROOT Java (saya akan berkomentar tentang itu di sana).
akauppi
ini sempurna jika Anda hanya perlu merender nilainya dan tidak pernah menggunakannya dalam operasi selanjutnya. terima kasih
Alexander Arendar
3
ini sesuatu yang lucu: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi
12

Bagaimana tentang :

 val value = 1.4142135623730951

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

//4 decimal places
println((value * 10000).round / 10000.toDouble)
langit biru
sumber
solusi yang cukup bersih. Ini milik saya untuk pemotongan: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoubletidak mengujinya terlalu banyak. Kode ini akan melakukan 2 tempat desimal.
robert
7

Edit: perbaiki masalah yang ditunjukkan @ryryguy. (Terima kasih!)

Kalau mau cepat, Kaito punya ide yang tepat. math.powlambat. Untuk penggunaan standar apa pun, Anda lebih baik menggunakan fungsi rekursif:

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

Ini sekitar 10x lebih cepat jika Anda berada dalam kisaran Long(yaitu 18 digit), sehingga Anda dapat membulatkan di mana saja antara 10 ^ 18 dan 10 ^ -18.

Rex Kerr
sumber
3
Hati-hati, mengalikan dengan timbal balik tidak bekerja dengan andal, karena mungkin tidak dapat direpresentasikan secara andal sebagai penggandaan: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Bagilah dengan signifikansi sebagai gantinya:math.round(x*100000)/100000.0
ryryguy
Mungkin juga berguna untuk mengganti p10fungsi rekursif dengan pencarian array: array akan meningkatkan konsumsi memori sekitar 200 byte tetapi kemungkinan akan menghemat beberapa iterasi per panggilan.
Levi Ramsey
4

Anda dapat menggunakan kelas implisit:

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))
}
Mitrakov Artem
sumber
1
Tentukan bahwa metode ini hanya untuk memotong dan tidak membulatkan dengan benar.
bobo32
4

Bagi yang tertarik, berikut adalah beberapa solusi yang disarankan ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

Pemotongan adalah yang tercepat, diikuti oleh BigDecimal. Perlu diingat pengujian ini dilakukan dengan menjalankan norma scala execution, tidak menggunakan alat benchmarking apapun.

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

}
cevaris
sumber
1
ScalaCustom yang terhormat tidak membulatkan, itu hanya memotong
Ravinder Payal
hmm, OP tidak spesifik untuk pembulatan atau pemotongan; ...truncate or round a Double.
cevaris
Namun menurut saya, membandingkan kecepatan / waktu eksekusi fungsi pemotongan dengan fungsi pembulatan kurang memadai. Itu sebabnya saya meminta Anda untuk menjelaskan kepada pembaca bahwa fitur kustom hanya terpotong. Dan fungsi truncate / custom yang disebutkan oleh Anda dapat disederhanakan lebih lanjut. val doubleParts = double. toString.split (".") Sekarang dapatkan dua karakter pertama dari doubleParts.taildan concat dengan string "." dan doubleParts. headdan parse menjadi double.
Ravinder Payal
1
diperbarui, terlihat lebih baik? juga saran toString.split(".")dan doubleParts.head/tailsaran Anda mungkin mengalami alokasi array tambahan ditambah penggabungan string. perlu menguji untuk memastikannya.
cevaris
3

Baru-baru ini, saya menghadapi masalah serupa dan saya menyelesaikannya dengan menggunakan pendekatan berikut

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)

Saya menggunakan masalah SO ini . Saya memiliki beberapa fungsi yang kelebihan beban untuk opsi Float \ Double dan implisit \ eksplisit. Perhatikan bahwa, Anda perlu menyebutkan secara eksplisit jenis kembalian jika fungsi kelebihan beban.

Khalid Saifullah
sumber
Juga, Anda dapat menggunakan pendekatan @ rex-kerr untuk kekuatan alih-alih Math.pow
Khalid Saifullah
1

Saya tidak akan menggunakan BigDecimal jika Anda peduli dengan kinerja. BigDecimal mengonversi angka menjadi string dan kemudian menguraikannya kembali:

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

Saya akan tetap berpegang pada manipulasi matematika seperti yang disarankan Kaito .

bigonazzi
sumber
0

Agak aneh tapi bagus. Saya menggunakan String dan bukan 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
}
jafed
sumber
0

Anda dapat melakukan: Math.round(<double precision value> * 100.0) / 100.0 Tetapi Math.round adalah yang tercepat tetapi rusak parah dalam kasus sudut dengan jumlah tempat desimal yang sangat tinggi (misalnya bulat (1000.0d, 17)) atau bagian bilangan bulat besar (misalnya round (90080070060.1d, 9) ).

Gunakan Desimal Besar, ini agak tidak efisien karena mengubah nilai menjadi string tetapi lebih relieval: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() gunakan preferensi mode Rounding Anda.

Jika Anda penasaran dan ingin mengetahui lebih detail mengapa hal ini terjadi, Anda dapat membaca ini: masukkan deskripsi gambar di sini

frostcs
sumber