Скала пар, і точність


118

Чи є функція, яка може обрізати або обводити Double? В один момент мого коду я хотів би, щоб число на зразок: 1.23456789було округлене до1.23


9
Переглянувши всі відповіді, я здогадуюсь, що коротка відповідь - ні? :)
Марселл Уоллес

4
@Gevorg Ти змусив мене сміятися. Я новачок у Скалі з інших важких числових мов, і моя щелепа ледь не потрапила в підлогу, читаючи цю нитку. Це божевільний стан справ для мови програмування.
ely

Відповіді:


150

Ви можете використовувати scala.math.BigDecimal:

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

Існує ряд інших режимів округлення , які, на жаль, не дуже добре зафіксовані в даний час (хоча їх еквіваленти Java є ).


5
Досить ймовірно, я б сказав. Все, що стосується сіток або фінансів, може вимагати округлення, а також продуктивності.
Рекс Керр

28
Я припускаю, що є люди, для яких тривалий дзвінок до незграбної бібліотеки зрозуміліший, ніж проста математика. Я б рекомендував "%.2f".format(x).toDoubleу такому випадку. Тільки в 2 рази повільніше, і вам залишається лише користуватися бібліотекою, яку ви вже знаєте.
Рекс Керр

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.
Рекс Керр

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

Або для будь-якого подвійного 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) ні?
C4stor

здається, це повертає неправильний результат NaN, чи не так?
jangorecki

проблема в floorтому, що 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
Я б не рекомендував це, хоча це моя власна відповідь: ті ж питання неточності, які згадував @ryryguy в коментарі іншої відповіді, впливають і тут. Використовуйте string.format з мовою Java ROOT (я про це коментую).
akauppi

це ідеально, якщо вам просто потрібно відобразити значення і ніколи не використовувати його в наступних операціях. спасибі
Олександр Арендар

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хоч не перевіряв її занадто багато. Цей код мав би два десяткових знаки.
Роберт

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

Це приблизно в 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 байт, але, ймовірно, заощадить кілька ітерацій на виклик.
Леві Рамзі

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. Майте на увазі, що ці випробування були виконані з виконанням нормального масштабування, не використовуючи жодних інструментів порівняльного аналізу.

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

хм, ОП не було специфічним для округлення або обрізання; ...truncate or round a Double.
cevaris

Але, на мій погляд, порівняння швидкості / часу виконання функції обрізання функції із функціями округлення є недостатньою. Ось чому я попросив вас пояснити читачеві, що користувацька функція лише скорочується. А згадана вами функція скорочення / користування може бути спрощена далі. val doubleЧасти = подвійний. toString.split (".") Тепер отримайте перші два символи doubleParts.tailта concat з рядками ". і doubleParts. headпроаналізуйте, щоб подвоїтись.
Равіндер Пайал

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)

Я використав цю проблему. У мене є кілька перевантажених функцій як для опцій Float \ Double, так і для неявних \ явних. Зауважте, що вам потрібно чітко вказати тип повернення у разі перевантаження функцій.


Також ви можете використовувати підхід @ rex-kerr для влади замість Math.pow
Халід Сайфулла,


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 є найшвидшим, але він погано розбивається у кутових випадках з дуже великою кількістю десяткових знаків (наприклад, кругла (1000.0d, 17)) або великою цілою частиною (наприклад, кругла (90080070060.1d, 9) ).

Використовуйте Bigdecimal, це трохи неефективно, оскільки він перетворює значення в рядки, але більше полегшення: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() використовуйте свої переваги режиму Округлення.

Якщо вам цікаво і хочете дізнатися детальніше, чому це відбувається, ви можете прочитати це: введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.