Scala Double và Precision


117

Có một hàm có thể cắt ngắn hoặc làm tròn một Double không? Tại một thời điểm trong mã của tôi, tôi muốn một số như: 1.23456789được làm tròn thành1.23


9
Sau khi xem tất cả các câu trả lời, tôi đoán rằng câu trả lời ngắn gọn là không? :)
Marsellus Wallace

4
@Gevorg Bạn đã làm tôi cười. Tôi mới làm quen với Scala từ các ngôn ngữ nặng về số học khác và hàm của tôi gần như chạm sàn khi đọc chủ đề này. Đây là một trạng thái điên rồ đối với một ngôn ngữ lập trình.
ely

Câu trả lời:


150

Bạn có thể sử dụng scala.math.BigDecimal:

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

Có một số chế độ làm tròn khác , tiếc là hiện tại không được ghi chép đầy đủ (mặc dù các chế độ tương đương Java của chúng là như vậy ).


5
Tôi nói khá có thể. Bất cứ điều gì liên quan đến lưới hoặc tài chính có thể yêu cầu làm tròn và cũng như hiệu suất.
Rex Kerr

27
Tôi cho rằng có những người mà cuộc gọi dài đến một thư viện ồn ào còn dễ hiểu hơn là toán học đơn giản. Tôi muốn giới thiệu "%.2f".format(x).toDoubletrong trường hợp đó. Chỉ chậm hơn 2 lần và bạn chỉ phải sử dụng một thư viện mà bạn đã biết.
Rex Kerr

6
@RexKerr, bạn không làm tròn trong trường hợp này .. chỉ đơn giản là cắt bớt.
José Leal

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

5
@RexKerr Tôi thích cách string.format của bạn hơn, nhưng trong ngôn ngữ sa mine (tiếng Phần Lan), cần phải cẩn thận để sửa sang ngôn ngữ ROOT. Ví dụ: "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Có vẻ như, định dạng sử dụng ',' vì ngôn ngữ trong khi toDouble không thể lấy nó và ném một NumberFormatException. Tất nhiên, điều này dựa trên nơi mã của bạn đang được chạy , không phải nơi nó được phát triển.
akauppi

77

Đây là một giải pháp khác không có BigDecimals

Cắt ngắn:

(math floor 1.23456789 * 100) / 100

Tròn:

(math rint 1.23456789 * 100) / 100

Hoặc đối với n kép và p chính xác bất kỳ:

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

Tương tự có thể được thực hiện cho chức năng làm tròn, lần này bằng cách sử dụng cà ri:

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

cái nào có thể tái sử dụng nhiều hơn, ví dụ như khi làm tròn số tiền, có thể sử dụng những thứ sau:

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

8
roundAt2 nên def roundAt2 (n: Double) = roundAt (2) (n) không?
C4stor

điều này dường như trả về kết quả không chính xác cho NaN, phải không?
jangorecki

vấn đề với floortruncateAt(1.23456789, 8)sẽ trở lại 1.23456788trong khi roundAt(1.23456789, 8)sẽ trả về giá trị đúng1.23456789
Todor Kolev

35

Vì chưa có ai đề cập đến %nhà điều hành, nên đến đây. Nó chỉ thực hiện việc cắt ngắn và bạn không thể dựa vào giá trị trả về để không có dấu chấm động không chính xác, nhưng đôi khi nó rất tiện dụng:

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

2
Tôi sẽ không đề xuất điều này mặc dù đó là câu trả lời của riêng tôi: các vấn đề không chính xác tương tự như được đề cập bởi @ryryguy trong nhận xét của một câu trả lời khác cũng ảnh hưởng ở đây. Sử dụng string.format với ngôn ngữ Java ROOT (Tôi sẽ bình luận về điều đó ở đó).
akauppi

điều này là hoàn hảo nếu bạn chỉ cần hiển thị giá trị và không bao giờ sử dụng nó trong các hoạt động tiếp theo. cảm ơn
Alexander Arendar

3
đây là một cái gì đó buồn cười: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi

12

Làm thế nào về :

 val value = 1.4142135623730951

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

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

dung dịch khá sạch. Đây là của tôi để cắt ngắn: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoublemặc dù không kiểm tra nó quá nhiều. Mã này sẽ làm 2 chữ số thập phân.
robert

7

Chỉnh sửa: đã khắc phục sự cố mà @ryryguy đã chỉ ra. (Cảm ơn!)

Nếu bạn muốn nó nhanh chóng, Kaito có ý tưởng phù hợp. math.powlà chậm, mặc dù. Đối với bất kỳ mục đích sử dụng tiêu chuẩn nào, bạn nên sử dụng hàm đệ quy:

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

Điều này nhanh hơn khoảng 10 lần nếu bạn nằm trong phạm vi Long(tức là 18 chữ số), vì vậy bạn có thể làm tròn ở bất kỳ đâu trong khoảng từ 10 ^ 18 đến 10 ^ -18.


3
Hãy coi chừng, nhân với nghịch đảo không hoạt động đáng tin cậy, vì nó có thể không đáng tin cậy để biểu diễn dưới dạng nhân đôi: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Chia cho tầm quan trọng thay vì:math.round(x*100000)/100000.0
ryryguy

Nó cũng có thể hữu ích khi thay thế p10hàm đệ quy bằng tra cứu mảng: mảng sẽ tăng mức tiêu thụ bộ nhớ khoảng 200 byte nhưng có khả năng tiết kiệm một số lần lặp cho mỗi cuộc gọi.
Levi Ramsey,

4

Bạn có thể sử dụng các lớp ngầm định:

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
Vui lòng chỉ rõ rằng phương pháp này chỉ để cắt ngắn và không làm tròn đúng cách.
bobo32

4

Đối với những người quan tâm, đây là một số thời gian cho các giải pháp được đề xuất ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

Cắt ngắn là nhanh nhất, tiếp theo là BigDecimal. Hãy nhớ rằng các bài kiểm tra này được thực hiện khi chạy thực thi theo tỷ lệ chuẩn, không sử dụng bất kỳ công cụ đo điểm chuẩn nào.

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
Thưa scalaCustom không làm tròn, nó chỉ cắt xén
Ravinder Payal

hmm, OP không dành riêng cho việc làm tròn hoặc cắt bớt; ...truncate or round a Double.
cevaris

Nhưng theo tôi việc so sánh tốc độ / thời gian thực hiện của hàm cắt ngắn với các hàm làm tròn là không thỏa đáng. Đó là lý do tại sao tôi yêu cầu bạn làm rõ với người đọc rằng tính năng tùy chỉnh chỉ cắt bớt. Và hàm cắt ngắn / tùy chỉnh mà bạn đề cập có thể được đơn giản hóa thêm. val doubleParts = gấp đôi. toString.split (".") Bây giờ lấy hai ký tự đầu tiên của doubleParts.tailvà nối với chuỗi "." và doubleParts. headvà phân tích cú pháp thành gấp đôi.
Ravinder Payal

1
cập nhật, trông đẹp hơn? cũng như đề xuất toString.split(".")và gợi ý của bạn doubleParts.head/tailcó thể bị phân bổ thêm mảng cộng với nối chuỗi. sẽ cần phải kiểm tra để chắc chắn.
cevaris

3

Gần đây, tôi gặp phải vấn đề tương tự và tôi đã giải quyết nó bằng cách tiếp cận sau

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)

Tôi sử dụng này vấn đề SO. Tôi có một vài hàm quá tải cho cả tùy chọn Float \ Double và implicit \ explicit. Lưu ý rằng, bạn cần phải đề cập rõ ràng kiểu trả về trong trường hợp các hàm quá tải.


Ngoài ra, bạn có thể sử dụng phương pháp tiếp cận của @ rex-kerr cho sức mạnh thay vì Math.pow
Khalid Saifullah.


1

Tôi sẽ không sử dụng BigDecimal nếu bạn quan tâm đến hiệu suất. BigDecimal chuyển đổi số thành chuỗi và sau đó phân tích cú pháp lại nó:

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

Tôi sẽ gắn bó với các thao tác toán học như Kaito đề nghị.


0

Hơi lạ nhưng hay. Tôi sử dụng Chuỗi chứ không phải 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
}

0

Bạn có thể làm:Math.round(<double precision value> * 100.0) / 100.0 Nhưng Math.round là nhanh nhất nhưng nó bị hỏng nặng trong các trường hợp góc có số lượng vị trí thập phân rất cao (ví dụ: tròn (1000.0d, 17)) hoặc phần nguyên lớn (ví dụ: tròn (90080070060.1d, 9) ).

Sử dụng Bigdecimal nó hơi không hiệu quả vì nó chuyển đổi các giá trị thành chuỗi nhưng nhẹ hơn: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() sử dụng tùy chọn của bạn về chế độ Làm tròn.

Nếu bạn tò mò và muốn biết thêm chi tiết tại sao điều này xảy ra, bạn có thể đọc phần này: nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.