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

Richsoni
fuente
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 ).

Travis Brown
fuente
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)
Kaito
fuente
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
akauppi
fuente
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)
cielo azul
fuente
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.

Rex Kerr
fuente
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))
}
Mitrakov Artem
fuente
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())
      }
    }
  }

}
cevaris
fuente
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.

Khalid Saifullah
fuente
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 .

Bigonazzi
fuente
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
}
jafed
fuente
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í

heladas
fuente