Scala Doubles และ Precision


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

มีโหมดการปัดเศษอื่น ๆ อีกมากมายซึ่งน่าเสียดายที่ในปัจจุบันยังไม่ได้รับการบันทึกไว้เป็นอย่างดี (แม้ว่าจะเทียบเท่ากับ Javaก็ตาม)


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

5
@RexKerr ฉันชอบวิธีสตริงรูปแบบของคุณ แต่ในภาษาของฉัน (ภาษาฟินแลนด์) ต้องใช้ความระมัดระวังในการแก้ไขเป็นภาษาราก เช่น "% .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

หรือสำหรับ 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 ควรเป็น def roundAt2 (n: Double) = roundAt (2) (n) no?
C4stor

ดูเหมือนจะส่งคืนผลลัพธ์ที่ไม่ถูกต้อง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 กล่าวไว้ในความคิดเห็นของคำตอบอื่นก็ส่งผลต่อที่นี่เช่นกัน ใช้ string.format กับ Java ROOT locale (ฉันจะแสดงความคิดเห็นเกี่ยวกับที่นั่น)
akauppi

เหมาะอย่างยิ่งหากคุณเพียงแค่ต้องการแสดงค่าและไม่เคยใช้ในการดำเนินการครั้งต่อไป ขอบคุณ
Alexander 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 ตำแหน่ง
robert

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

จะเร็วขึ้นประมาณ 10 เท่าหากคุณอยู่ในช่วงLong(เช่น 18 หลัก) ดังนั้นคุณสามารถปัดเศษตรงจุดใดก็ได้ระหว่าง 10 ^ 18 ถึง 10 ^ -18


3
ระวังคูณด้วยซึ่งกันและกันไม่ได้ทำงานได้อย่างน่าเชื่อถือเพราะมันอาจจะไม่น่าเชื่อถือซึ่งแสดงเป็นคู่: ==>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 = สองเท่า 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 และตัวเลือกโดยนัย \ Explicit โปรดทราบว่าคุณต้องระบุประเภทการส่งคืนอย่างชัดเจนในกรณีที่มีฟังก์ชันมากเกินไป


นอกจากนี้คุณอาจใช้วิธีการของ @ rex-kerr แทนการใช้ Math.pow
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)

ฉันจะยึดติดกับการคำนวณทางคณิตศาสตร์ตามที่ไคโตะแนะนำ


0

แปลกนิดหน่อย แต่ก็ดี ฉันใช้ String ไม่ใช่ 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

คุณสามารถทำได้: 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.