Scala Doubles ve Precision


118

Bir Çifti kesebilen veya yuvarlayabilen bir işlev var mı? Kodumun bir noktasında şunun gibi bir sayı istiyorum: 1.23456789yuvarlanacak1.23


9
Tüm cevaplara baktıktan sonra sanırım kısa cevap hayır mı? :)
Marsellus Wallace

4
@Gevorg Beni güldürdün. Diğer sayısal ağırlıklı dillerden Scala'da yeniyim ve çenem bu ipliği okuyarak neredeyse yere çarptı. Bu, bir programlama dili için çılgınca bir durumdur.
ely

Yanıtlar:


150

Şunları kullanabilirsiniz scala.math.BigDecimal:

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

Maalesef şu anda çok iyi belgelenmemiş ( Java eşdeğerleri olsa da ) bir dizi başka yuvarlama modu vardır .


5
Oldukça muhtemel diyebilirim. Şebekeleri veya finansmanı içeren her şey, yuvarlama ve ayrıca performans gerektirebilir.
Rex Kerr

28
Sanırım hantal bir kütüphaneye uzun bir çağrı yapmanın basit matematikten daha anlaşılır olduğu insanlar var. "%.2f".format(x).toDoubleBu durumda tavsiye ederim . Yalnızca 2 kat daha yavaştır ve yalnızca zaten bildiğiniz bir kitaplığı kullanmanız gerekir.
Rex Kerr

6
@RexKerr, bu durumda yuvarlama yapmıyorsun .. basitçe kırpıyorsun.
José Leal

16
@ JoséLeal - Huh? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71ama scala> "%.2f".format(0.715).toDouble res14: Double = 0.72.
Rex Kerr

5
@RexKerr string.format yönteminizi tercih ederim, ancak yerel ayarlarda benimki (Fince), ROOT yerel ayarını düzeltmek için özen gösterilmelidir. Ör. "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Görünüşe göre format yerel ayar nedeniyle ',' kullanıyor, oysa toDouble onu içeri alamıyor ve bir NumberFormatException atıyor. Bu elbette kodunuzun nerede geliştirildiğine değil, nerede çalıştırıldığına bağlıdır .
akauppi

77

İşte BigDecimals olmadan başka bir çözüm

kırp:

(math floor 1.23456789 * 100) / 100

Yuvarlak:

(math rint 1.23456789 * 100) / 100

Veya herhangi bir çift n ve hassas p için:

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

Yuvarlama işlevi için de benzer bir işlem yapılabilir, bu sefer körleme kullanılarak:

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

bu daha çok tekrar kullanılabilir, örneğin para tutarlarını yuvarlarken aşağıdakiler kullanılabilir:

def roundAt2(n: Double) = roundAt(2)(n)

8
roundAt2 def roundAt2 olmalıdır (n: Double) = roundAt (2) (n) hayır?
C4stor

bu yanlış sonuç veriyor gibi görünüyor NaN, değil mi?
jangorecki

sorun floorDİR truncateAt(1.23456789, 8)dönecektir 1.23456788ise roundAt(1.23456789, 8)doğru değeri döndürecektir1.23456789
Todor Kolev

35

%Henüz kimse operatörden bahsetmediğine göre , işte geliyor. Yalnızca kısaltma yapar ve kayan nokta yanlışlıklarının olmaması için dönüş değerine güvenemezsiniz, ancak bazen kullanışlıdır:

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

2
Kendi cevabım olmasına rağmen bunu tavsiye etmem: @ryryguy tarafından başka bir cevabın yorumunda belirtildiği gibi aynı yanlışlık sorunları burada da etkiliyor. String.format'ı Java ROOT yerel ayarıyla kullanın (orada bunun hakkında yorum yapacağım).
akauppi

Bu, yalnızca değeri oluşturmanız ve sonraki işlemlerde asla kullanmamanız gerekiyorsa mükemmeldir. teşekkürler
Alexander Arendar

3
işte komik bir şey: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi

12

Peki ya:

 val value = 1.4142135623730951

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

//4 decimal places
println((value * 10000).round / 10000.toDouble)

oldukça temiz çözüm. İşte kısaltmak için benim: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoubleyine de çok fazla test etmedim. Bu kod 2 ondalık basamak yapar.
robert

7

Düzenleme: @ryryguy'un işaret ettiği sorunu çözdü. (Teşekkürler!)

Hızlı olmasını istiyorsanız, Kaito'nun doğru fikri var. math.powyavaş olsa da. Herhangi bir standart kullanım için özyinelemeli bir işlevle daha iyi durumda olursunuz:

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

Bu, Long(yani 18 basamaklı) aralığındaysanız yaklaşık 10 kat daha hızlıdır , böylece 10 ^ 18 ile 10 ^ -18 arasında herhangi bir yerde yuvarlayabilirsiniz.


3
Dikkat edin, karşılıklı ile çarpmak güvenilir bir şekilde çalışmaz, çünkü ikili olarak güvenilir bir şekilde temsil edilemeyebilir: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Bunun yerine math.round(x*100000)/100000.0
önemle bölün

Özyinelemeli p10işlevi bir dizi aramasıyla değiştirmek de yararlı olabilir : dizi bellek tüketimini yaklaşık 200 bayt artıracak, ancak muhtemelen çağrı başına birkaç yineleme kaydedecektir.
Levi Ramsey

4

Örtük sınıfları kullanabilirsiniz:

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

1
Lütfen bu yöntemin yalnızca kesmek için olduğunu ve düzgün yuvarlama yapmadığını belirtin.
bobo32

4

İlgilenenler için, işte önerilen çözümler için bazı zamanlar ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

Kesme en hızlı olanıdır ve onu BigDecimal izler. Unutmayın, bu testler herhangi bir kıyaslama aracı kullanılmadan norma scala çalıştırılarak yapıldı.

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

}

1
Sevgili scalaCustom yuvarlanmıyor, sadece kısalıyor
Ravinder Payal

hmm, OP yuvarlamaya veya kesmeye özgü değildi; ...truncate or round a Double.
cevaris

Ama benim görüşüme göre, kesme fonksiyonunun hız / uygulama zamanını yuvarlama fonksiyonları ile karşılaştırmak yetersizdir. Bu nedenle, okuyucuya özel özelliğin yalnızca kısalttığını açıklamanızı istedim. Bahsettiğiniz kesik / özel işlev daha da basitleştirilebilir. val doubleParts = double. toString.split (".") Şimdi ilk iki karakter doubleParts.tailve dizelerle birlikte "." ve doubleParts. headve ikiye bölün.
Ravinder Payal

1
güncellendi, daha iyi görünüyor mu? ayrıca öneri toString.split(".")ve doubleParts.head/tailöneriniz fazladan dizi tahsisi artı dize birleştirme sorunlarından da etkilenebilir. emin olmak için test etmeniz gerekir.
cevaris

3

Son zamanlarda benzer bir sorunla karşılaştım ve aşağıdaki yaklaşımı kullanarak çözdüm

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)

Kullandığım bu SO sorunu. Float \ Double ve örtük \ açık seçenekler için birkaç aşırı yüklenmiş işlevim var. Unutmayın, aşırı yüklenmiş işlevler durumunda dönüş türünü açıkça belirtmeniz gerekir.


Ayrıca, iktidar için Math.pow yerine @ rex-kerr'in yaklaşımını kullanabilirsiniz
Khalid Saifullah 18'13


1

Performansı önemsiyorsanız BigDecimal'ı kullanmam. BigDecimal, sayıları dizeye dönüştürür ve ardından tekrar ayrıştırır:

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

Kaito'nun önerdiği gibi matematik manipülasyonlarına bağlı kalacağım .


0

Biraz tuhaf ama hoş. String kullanıyorum, BigDecimal değil

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

Şunları yapabilirsiniz: Math.round(<double precision value> * 100.0) / 100.0 Ancak Math.round en hızlısıdır ancak çok yüksek sayıda ondalık basamaklı (ör. Yuvarlak (1000.0d, 17)) veya büyük tam sayı bölümü (ör. Yuvarlak (90080070060.1d, 9) olan köşe durumlarda kötü bir şekilde bozulur. ).

Bigdecimal kullanın, değerleri dizeye dönüştürdüğü için biraz verimsizdir, ancak daha güvenilirdir: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() Tercihinizi Yuvarlama modunu kullanın.

Merak ediyorsanız ve bunun neden olduğunu daha fazla bilmek istiyorsanız, şunu okuyabilirsiniz: görüntü açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.