Scalaのdoubleと精度


117

Doubleを切り捨てたり丸めたりできる関数はありますか?私のコードのある時点で、私は次のような数を望みます:1.23456789丸められる1.23


9
すべての答えを見た後、私は短い答えはノーだと思いますか?:)
Marsellus Wallace

4
@Gevorgあなたは私を笑わせました。私は他の数値の多い言語からScalaを初めて使用するので、私のスレッドはこのスレッドを読んで床にぶつかるところです。これは、プログラミング言語にとって非常識な状態です。
e'18年

回答:


150

使用できますscala.math.BigDecimal

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

他にも多くの丸めモードがありますが、残念ながら現在のところ十分に文書化されていません(Javaでの同等物はですが)。


5
かなり可能性があります。グリッドやファイナンスが関係するものはすべて、丸めとパフォーマンスも必要になる可能性があります。
Rex Kerr

27
単純な数学よりも、不器用なライブラリへの長い呼び出しのほうが理解しやすい人がいると思います。"%.2f".format(x).toDoubleその場合にお勧めします。速度が2倍遅く、すでに知っているライブラリを使用するだけで済みます。
Rex Kerr

6
@RexKerr、この場合は丸めません。単に切り捨てます。
ホセ・レアル

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

5
@RexKerr string.formatの方法を好みますが、ロケールがサミン(フィンランド語)の場合、ROOTロケールに修正するように注意する必要があります。例: "%.2f" .formatLocal(java.util.Locale.ROOT、x).toDouble。ロケールが原因でformatは '、'を使用しているようですが、toDoubleはそれを取り込むことができず、NumberFormatExceptionをスローします。もちろん、これは、コードが開発された場所ではなく、コードが実行されている場所に基づいています。
akauppi 2014

77

BigDecimalsを使用しない別のソリューションを次に示します

切り捨て:

(math floor 1.23456789 * 100) / 100

円形:

(math rint 1.23456789 * 100) / 100

または、任意のdouble nおよび精度pの場合:

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

丸め関数についても同様ですが、今回はカリーを使用します。

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

これは、より再利用可能です。たとえば、金額を丸める場合は、以下を使用できます。

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

8
roundAt2は定義する必要がありますroundAt2(n:Double)= roundAt(2)(n)no?
C4stor 2014

これはに対して誤った結果を返すようです。そうNaNではありませんか?
jangorecki

問題floorは、truncateAt(1.23456789, 8)が正しい値を返す1.23456788一方roundAt(1.23456789, 8)で、1.23456789
Todor Kolevが

35

誰も%まだオペレーターに言及していないので、ここに来ます。これは切り捨てを行うだけであり、戻り値を使用して浮動小数点が不正確にならないようにすることはできませんが、便利な場合があります。

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

2
私自身の回答ですが、これはお勧めしません。@ ryryguyが別の回答のコメントで言及したのと同じ不正確な問題が、ここにも影響します。Java ROOTロケールでstring.formatを使用します(ここでコメントします)。
akauppi 2014

これは、値をレンダリングする必要があるだけで、後続の操作で決して使用しない場合に最適です。ありがとう
Alexander Arendar 14

3
ここに面白いものがあります: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi 2015

12

どのように:

 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それをあまりテストしませんでした。このコードは小数点以下2桁を実行します。
ロバート

7

編集:@ryryguyが指摘した問題を修正しました。(ありがとう!)

あなたがそれを速くしたいなら、カイトは正しい考えを持っています。 math.powしかし、遅いです。標準的な使用では、再帰関数を使用するほうがよいでしょう。

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

これは、範囲内Long(つまり18桁)であれば約10倍高速であるため、10 ^ 18から10 ^ -18の間の任意の場所で丸めることができます。


3
逆数による乗算は、double:scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> として確実に表現できない可能性があるため、確実に機能しませんres12: Double = 0.023514999999999998。代わりに意義除算:math.round(x*100000)/100000.0
ryryguy

また、再帰p10関数を配列ルックアップに置き換えると便利な場合があります。配列によってメモリ消費量が約200バイト増加しますが、呼び出しごとに数回の繰り返しを節約できます。
Levi Ramsey

4

暗黙のクラスを使用できます。

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
このメソッドは切り捨て専用であり、適切に丸められないことを指定してください。
bobo32

4

興味のある方のために、ここに提案された解決策のいくつかの時間があります...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

切り捨てが最も速く、次にBigDecimalが続きます。これらのテストは、ベンチマークツールを使用せずに、標準のScala実行を実行して行われたことに注意してください。

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
親愛なるscalaCustomは四捨五入ではなく、単に切り捨てています
Ravinder Payal

うーん、OPは丸めや切り捨てに固有ではありませんでした。...truncate or round a Double
cevaris

しかし、私の意見では、切り捨て関数の速度/実行時間と丸め関数の比較は不十分です。そのため、カスタム機能では切り捨てられるだけであることを読者に説明するように依頼しました。そして、あなたが言及した切り捨て/カスタム関数はさらに簡略化することができます。val doubleParts = double。toString.split( "。")の最初の2文字を取得し、doubleParts.tail文字列 "。"と連結します。そしてdoubleParts. head、解析して2倍にします。
Ravinder Payal 2018

1
更新されました、よく見えますか?また、提案toString.split(".")doubleParts.head/tail提案は、余分な配列割り当てと文字列連結の影響を受ける可能性があります。確認するためにテストする必要があります。
cevaris

3

最近、私は同様の問題に直面し、次のアプローチを使用してそれを解決しました

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)

私はこの SOの問題を使用しました。Float \ Doubleオプションとimplicit \ explicitオプションの両方にオーバーロードされた関数がいくつかあります。関数がオーバーロードされている場合は、戻り値の型を明示的に言及する必要があることに注意してください。


また、Math.powの代わりに@ rex-kerrの手法を使用することもできます
Khalid Saifullah


1

パフォーマンスを重視する場合は、BigDecimalを使用しません。BigDecimalは数値を文字列に変換してから、再度解析します。

  /** 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が提案したように、私は数学の操作に固執します。


0

少し奇妙ですがいいです。BigDecimalではなくStringを使用しています

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

あなたができること:Math.round(<double precision value> * 100.0) / 100.0 しかし、Math.roundは最速ですが、小数点以下の桁数が非常に多い(例:round(1000.0d、17))または大きな整数部分(例:round(90080070060.1d、9)のいずれかが多いコーナーケースでは、うまく機能しません。)。

Bigdecimalを使用します。値を文字列に変換するため少し効率が悪くなりますが、より安全です。 BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() 使用します。です。丸めモードの設定を使用してください。

興味があって、なぜこれが起こるのか詳細を知りたいなら、これを読むことができます: ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.