web-dev-qa-db-ja.com

Scala Doubles、およびPrecision

Doubleを切り捨てたり丸めたりできる関数はありますか?私のコードのある時点で、1.23456789のような数値を1.23に丸めたい

105
richsoni

scala.math.BigDecimal を使用できます。

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

他にも多くの 四捨五入モード がありますが、残念ながら現時点ではあまり文書化されていません( それらのJava相当 )。

132
Travis Brown

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(p: Int) = roundAt(2)(p)
70
Kaito

%演算子についてはまだ誰も言及していないので、ここにきます。切り捨てのみを行い、戻り値に依存して浮動小数点の不正確性がないことはできませんが、便利な場合があります。

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

どうですか:

 val value = 1.4142135623730951

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

//4 decimal places
println((value * 10000).round / 10000.toDouble)
8
blue-sky

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

高速にしたい場合、Kaitoには正しい考えがあります。 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の間のどこでも丸めることができます。

7
Rex Kerr

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

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))
}
4
Mitrakov Artem

興味をお持ちの方のために、提案された解決策をいくつかご紹介します...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

切り捨てが最速で、それにBigDecimalが続きます。これらのテストは、ベンチマークツールを使用せずにnorma 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())
      }
    }
  }

}
4
cevaris

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

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)

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

3

パフォーマンスに関心がある場合、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 が示唆するように、私は数学の操作に固執するつもりです。

1
bigonazzi

できます: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()は、丸めモードの設定を使用します。

好奇心が強く、なぜこれが起こるのか詳細を知りたい場合は、これを読むことができます: enter image description here

0
frostcs

少し奇妙ですが、ニース。 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
jafed