Scala Dobles y Precisión


118

¿Existe una función que pueda truncar o redondear un Double? En un punto de mi código, me gustaría que un número como: 1.23456789se redondee a1.23


9
Después de ver todas las respuestas, supongo que la respuesta corta es no. :)
Marsellus Wallace

4
@Gevorg Me hiciste reír. Soy nuevo en Scala de otros lenguajes con muchos números y mi mandíbula casi golpea el suelo al leer este hilo. Este es un estado de locura para un lenguaje de programación.
ely

Respuestas:


150

Puede utilizar scala.math.BigDecimal:

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

Hay varios otros modos de redondeo , que desafortunadamente no están muy bien documentados en la actualidad (aunque sus equivalentes de Java sí lo están ).


5
Es bastante probable, diría yo. Cualquier cosa que involucre cuadrículas o finanzas puede requerir redondeo y también rendimiento.
Rex Kerr

28
Supongo que hay personas para quienes una larga llamada a una biblioteca torpe es más comprensible que las simples matemáticas. Lo recomendaría "%.2f".format(x).toDoubleen ese caso. Solo 2 veces más lento, y solo tiene que usar una biblioteca que ya conoce.
Rex Kerr

6
@RexKerr, no está redondeando en este caso ... simplemente truncando.
José Leal

16
@ JoséLeal - ¿Eh? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71pero scala> "%.2f".format(0.715).toDouble res14: Double = 0.72.
Rex Kerr

5
@RexKerr Prefiero su forma de string.format, pero en las configuraciones regionales como la mía (finlandés), se debe tener cuidado para corregir la configuración regional ROOT. Por ejemplo, "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Parece que el formato usa ',' debido a la configuración regional, mientras que toDouble no puede aceptarlo y arroja una NumberFormatException. Por supuesto, esto se basa en el lugar donde se ejecuta su código , no donde se desarrolla.
akauppi

77

Aquí hay otra solución sin BigDecimals

Truncar:

(math floor 1.23456789 * 100) / 100

Redondo:

(math rint 1.23456789 * 100) / 100

O para cualquier doble n y precisión p:

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

Se puede hacer algo similar para la función de redondeo, esta vez usando curado:

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

que es más reutilizable, por ejemplo, al redondear cantidades de dinero, se podría usar lo siguiente:

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

8
roundAt2 debe ser def roundAt2 (n: Double) = roundAt (2) (n) no?
C4stor

esto parece devolver un resultado incorrecto NaN, ¿no es así?
Jangorecki

el problema floores que truncateAt(1.23456789, 8)volverá 1.23456788mientras roundAt(1.23456789, 8)que devolverá el valor correcto de1.23456789
Todor Kolev

35

Como nadie mencionó al %operador todavía, aquí viene. Solo trunca, y no puede confiar en que el valor de retorno no tenga inexactitudes de punto flotante, pero a veces es útil:

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

2
No recomendaría esto, aunque es mi propia respuesta: los mismos problemas de inexactitud mencionados por @ryryguy en el comentario de otra respuesta también afectan aquí. Use string.format con la configuración regional Java ROOT (comentaré sobre eso allí).
akauppi

esto es perfecto si solo necesita representar el valor y nunca usarlo en operaciones posteriores. gracias
Alexander Arendar

3
aquí hay algo divertido: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi

12

Qué tal si :

 val value = 1.4142135623730951

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

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

solución bastante limpia. Aquí está el mío para el truncamiento: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoubleaunque no lo probé demasiado. Este código ocuparía 2 lugares decimales.
robert

7

Editar: solucionó el problema que señaló @ryryguy. (¡Gracias!)

Si quieres que sea rápido, Kaito tiene la idea correcta. math.powaunque es lento. Para cualquier uso estándar, es mejor que tenga una función recursiva:

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

Esto es aproximadamente 10 veces más rápido si está dentro del rango de Long(es decir, 18 dígitos), por lo que puede redondear en cualquier lugar entre 10 ^ 18 y 10 ^ -18.


3
Cuidado, multiplicar por el recíproco no funciona de manera confiable, porque puede no ser representable de manera confiable como un doble: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Dividir por el significado en su lugar:math.round(x*100000)/100000.0
ryryguy

También puede ser útil reemplazar la p10función recursiva con una búsqueda de matriz: la matriz aumentará el consumo de memoria en aproximadamente 200 bytes, pero probablemente ahorrará varias iteraciones por llamada.
Levi Ramsey

4

Puede usar clases implícitas:

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
Especifique que este método es solo para truncar y no para redondear correctamente.
bobo32

4

Para aquellos que estén interesados, aquí hay algunos momentos para las soluciones sugeridas ...

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

Truncation
Scala custom Formatter: Elapsed Time: 3 

El truncamiento es el más rápido, seguido de BigDecimal. Tenga en cuenta que estas pruebas se realizaron ejecutando la ejecución de norma scala, sin utilizar ninguna herramienta de evaluación comparativa.

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
Estimado scalaCustom no se está redondeando, solo se está truncando
Ravinder Payal

hmm, OP no era específico para redondear o truncar; ...truncate or round a Double.
cevaris

Pero en mi opinión, comparar la velocidad / tiempo de ejecución de la función de truncar con las funciones de redondeo es inadecuado. Por eso le pedí que le aclarara al lector que la función personalizada solo se trunca. Y la función truncar / personalizada mencionada por usted se puede simplificar aún más. val doubleParts = doble. toString.split (".") Ahora obtenga los dos primeros caracteres de doubleParts.taily concat con cadenas "." y doubleParts. heady analizar para duplicar.
Ravinder Payal

1
actualizado, se ve mejor? también su sugerencia toString.split(".")y doubleParts.head/tailsugerencia pueden sufrir una asignación de matriz adicional más la concatenación de cadenas. Sin embargo, necesitaría probarlo para estar seguro.
cevaris

3

Recientemente, enfrenté un problema similar y lo resolví usando el siguiente enfoque

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)

He utilizado este tema SO. Tengo un par de funciones sobrecargadas para las opciones Float \ Double e implícita \ explícita. Tenga en cuenta que debe mencionar explícitamente el tipo de retorno en caso de funciones sobrecargadas.


Además, puede usar el enfoque de @ rex-kerr para el poder en lugar de Math.pow
Khalid Saifullah


1

No usaría BigDecimal si te importa el rendimiento. BigDecimal convierte números en cadenas y luego los vuelve a analizar:

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

Voy a ceñirme a las manipulaciones matemáticas como sugirió Kaito .


0

Un poco extraño pero agradable. Yo uso String y no 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

Puede hacer: Math.round(<double precision value> * 100.0) / 100.0 Pero Math.round es más rápido pero se descompone mal en los casos de esquina con un número muy alto de lugares decimales (por ejemplo, round (1000.0d, 17)) o una parte entera grande (por ejemplo, round (90080070060.1d, 9) ).

Use Bigdecimal, es un poco ineficiente, ya que convierte los valores en una cadena, pero más relevación: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() use su preferencia del modo Redondeo.

Si tienes curiosidad y quieres saber más detalles por qué sucede esto puedes leer esto: ingrese la descripción de la imagen aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.