Scala Doubles 및 정밀도


118

Double을 자르거나 반올림 할 수있는 함수가 있습니까? :처럼 내 코드의 한 시점에서 나는 수를 싶습니다 1.23456789반올림 할1.23


9
모든 답을 보니 짧은 답은 '아니오'라고 생각합니다. :)
Marsellus Wallace

4
@Gevorg 당신은 나를 웃게 만들었습니다. 나는 다른 숫자가 많은 언어에서 Scala를 처음 접했고 내 턱이이 스레드를 읽고 거의 바닥에 부딪쳤다. 이것은 프로그래밍 언어에 대한 미친 상태입니다.
ely

답변:


150

다음을 사용할 수 있습니다 scala.math.BigDecimal.

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

다른 많은 반올림 모드가 있습니다 . 불행히도 현재 문서화가 잘되어 있지 않습니다 ( 자바에 해당하는 것은 ).


5
상당히 가능성이 있습니다. 그리드 또는 금융과 관련된 모든 것은 반올림과 성능이 필요할 수 있습니다.
Rex Kerr

28
단순한 수학보다 투박한 도서관에 대한 긴 전화가 더 이해하기 쉬운 사람들이 있다고 생각합니다. "%.2f".format(x).toDouble이 경우 추천 합니다. 2 배 더 느리고 이미 알고있는 라이브러리 만 사용하면됩니다.
Rex Kerr

6
@RexKerr,이 경우에는 반올림하지 않습니다. 단순히 잘라내기만하면됩니다.
José Leal

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 2013

5
@RexKerr 나는 당신의 string.format 방식을 선호하지만 로케일 내 (핀란드어)에서는 ROOT 로케일을 수정하기 위해주의를 기울여야합니다. 예 : "% .2f".formatLocal (java.util.Locale.ROOT, x) .toDouble. 형식은 로케일 때문에 ','를 사용하는 반면 toDouble은 그것을 가져올 수 없으며 NumberFormatException을 던집니다. 물론 이것은 코드가 개발 된 위치 가 아니라 실행 되는 위치를 기반으로합니다 .
akauppi

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 }

이번에는 currying을 사용하여 반올림 함수에 대해서도 유사하게 수행 할 수 있습니다.

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는 def이어야합니다. roundAt2 (n : Double) = roundAt (2) (n) no?
C4stor 2014 년

에 대한 잘못된 결과를 반환하는 것 같지 NaN않습니까?
jangorecki

의 문제 floorIS truncateAt(1.23456789, 8)반환 1.23456788하면서 roundAt(1.23456789, 8)올바른 값을 반환합니다1.23456789
토 도르 콜 레브

35

%아직 아무도 운영자를 언급 하지 않았으므로 여기에옵니다. 자르기 만 수행하며 부동 소수점 부정확성을 갖지 않기 위해 반환 값에 의존 할 수는 없지만 때로는 편리합니다.

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

2
내 자신의 대답이지만 이것을 권장하지 않을 것입니다. Java ROOT 로케일과 함께 string.format을 사용하십시오 (여기에서 이에 대해 설명하겠습니다).
akauppi

이는 값을 렌더링하고 후속 작업에서 절대 사용하지 않는 경우에 완벽합니다. 감사
알렉산더 Arendar

3
여기에 뭔가 재미 : 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi

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가 지적한 문제를 수정했습니다. (감사!)

빠른 속도를 원한다면 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 사이에서 반올림 할 수 있습니다.


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이 그 뒤를 따릅니다. 이 테스트는 벤치마킹 도구를 사용하지 않고 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())
      }
    }
  }

}

1
친애하는 scalaCustom는 그냥 잘라내는 것, 사사 오입되지 않는다
Ravinder하기 Payal

흠, OP는 반올림 또는 자르기에만 국한되지 않았습니다. ...truncate or round a Double.
cevaris

그러나 내 의견으로는 자르기 기능의 속도 / 실행 시간을 반올림 기능과 비교하는 것은 부적절합니다. 그래서 독자들에게 커스텀 기능이 잘릴뿐임을 분명히 해달라고 요청한 것입니다. 그리고 당신이 언급 한 자르기 / 사용자 정의 기능을 더 단순화 할 수 있습니다. val doubleParts = double. toString.split ( ".") 이제 처음 두 문자를 가져와 doubleParts.tail"."문자열과 연결합니다. 그리고 doubleParts. head두 배로 구문 분석합니다.
Ravinder Payal

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.