Вопрос по rounding, scala, double, truncate – Скала удваивается и точность

98

Есть ли функция, которая может обрезать или округлить Double? В какой-то момент в моем коде я хотел бы число как:1.23456789 округляться до1.23

Посмотрев на все ответы, я думаю, что короткий ответ - нет? :) Gevorg
@Gevorg Ты заставил меня смеяться. Я новичок в Scala из других числовых языков, и моя челюсть почти упала на пол, читая эту ветку. Это безумное положение вещей для языка программирования. ely

Ваш Ответ

10   ответов
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
}
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))
}
Пожалуйста, укажите, что этот метод предназначен только для усечения, а не для правильного округления.
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.

Также может быть полезно заменить рекурсивныйp10 функция с поиском в массиве: массив увеличит потребление памяти примерно на 200 байт, но, скорее всего, сэкономит несколько итераций на вызов.
Остерегайтесь, умножение на обратное не работает надежно, потому что оно не может быть надежно представлено в виде двойного:scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515) == & GT;res12: Double = 0.023514999999999998, Вместо этого разделите на значимость:math.round(x*100000)/100000.0
8

 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 десятичных знака.
1

если вы заботитесь о производительности. 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 предложил.

29

% Оператор еще, здесь приходит. Он только выполняет усечение, и вы не можете полагаться на возвращаемое значение, чтобы не иметь погрешностей с плавающей запятой, но иногда это удобно:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
вот что-то смешное26.257391515826225 - 0.057391515826223094 = 26.200000000000003
Не рекомендую это, хотя это мой собственный ответ: те же самые проблемы с неточностью, о которых упоминал @ryryguy в комментарии другого ответа, также влияют и здесь. Используйте string.format с языковым стандартом Java ROOT (об этом я буду комментировать).
это идеально, если вам просто нужно отобразить значение и никогда не использовать его в последующих операциях. Спасибо
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.
3

кто заинтересован, вот несколько раз для предлагаемых решений ...

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

}
хм, OP не был специфичен для округления или усечения;...truncate or round a Double.
Уважаемый scalaCustom не округляется, он просто усекается
обновился, лучше посмотришь? также ваше предложениеtoString.split(".") а такжеdoubleParts.head/tail предложение может пострадать от дополнительного выделения массива плюс конкатенация строк. нужно будет проверить, чтобы быть уверенным, хотя.
Но, на мой взгляд, сравнение скорости / времени выполнения функции усечения с функциями округления неадекватно. Вот почему я попросил вас разъяснить читателю, что пользовательская функция только усекается. Упомянутая вами усеченная / настраиваемая функция может быть упрощена в дальнейшем. val doubleParts = double. toString.split (& quot;. & quot;). Теперь получите первые два символаdoubleParts.tail и согласовать со строками "." а такжеdoubleParts. head и разобрать, чтобы удвоить.
69

Обрезание:

(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(p: Int) = roundAt(2)(p)
roundAt2 должен быть определен roundAt2 (n: Double) = roundAt (2) (n) нет?
проблема сfloor в том, чтоtruncateAt(1.23456789, 8) вернусь1.23456788 в то время какroundAt(1.23456789, 8) вернет правильное значение1.23456789
это, кажется, возвращает неверный результат дляNaNне так ли?
119

scala.math.BigDecimal:

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

Есть ряд другихрежимы округлениячто, к сожалению, в настоящее время не очень хорошо документировано (хотяих Java-эквиваленты).

@RexKerr, в этом случае вы не округляете .. просто усекаете.
Я предполагаю, что есть люди, для которых долгий вызов неуклюжей библиотеки более понятен, чем простая математика. Я рекомендую"%.2f".format(x).toDouble в таком случае. Только в 2 раза медленнее, и вам нужно использовать только библиотеку, которую вы уже знаете.
@RexKerr Я предпочитаю ваш метод string.format, но в локалях s.a. мой (финский), нужно позаботиться о том, чтобы починить ROOT локаль. Например. "%. 2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Похоже, формат использует «,» из-за локали, тогда как toDouble не может принять его и выдает исключение NumberFormatException. Это, конечно, зависит от того, где находится ваш кодrunне там, где оно разработано.
Скорее всего, я бы сказал. Все, что связано с сетками или финансами, может потребовать округления, а также производительности.
@ Jos & # xE9; Лил, а?scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71 ноscala> "%.2f".format(0.715).toDouble res14: Double = 0.72.

Похожие вопросы