¿Hay alguna forma de hacer BigDecimal más rápido que aquí?

Estoy trabajando en un software de análisis financiero que necesitará procesar volúmenes grandes (ish) de datos. Me gustaría utilizar BigDecimal para la precisión (cierta información de precios va a cuatro o cinco dígitos a la derecha del decimal) pero me preocupaba la velocidad.

Escribí la siguiente aplicación de testing y parece que BigDecimal puede ser de 90 a 100 veces más lento que Dobles. Sabía que habría un delta, pero eso es más de lo que esperaba. Aquí hay un resultado típico después de muchas testings.

BigDecimal took 17944 ms Double took 181 ms 

¿Me estoy perdiendo de algo?

Aquí está el código. Traté de hacerlo representativo del mundo real. Creé una constante donde pude (pi) pero también hice algunas matemáticas en línea de numbers que variarían de la fila de datos a la fila de datos, como pi * BigDecimal (i) + BigDecimal (1). Mi punto es que evitar constructores no puede ser la única respuesta.

Afortunadamente, parece que Double tiene la precisión suficiente, ya que los numbers generalmente estarán en el formatting 00000.00000. ¿Alguno de los errores ocultos que debería saber? ¿Las personas usan Double para el software de análisis financiero?

 import java.math.BigDecimal object Stopwatch { inline fun elapse(f: () -> Unit):Long { val start = System.currentTimeMillis() f() return System.currentTimeMillis() - start } } fun tryBigDecimal() { val arr: MutableList<BigDecimal> = arrayListOf() for (i in 1..10000000) { arr.add(BigDecimal(i)) } val pi = BigDecimal(3.14159) for (i in 0..arr.size - 1) { arr[i] = arr[i] * pi / (pi * BigDecimal(i) + BigDecimal(1)) } //arr.forEachIndexed { i, bigDecimal -> println("$i, ${bigDecimal.toString()}")} } fun tryDouble() { val arr: MutableList<Double> = arrayListOf() for (i in 1..10000000) { arr.add(i.toDouble()) } val pi = 3.14159 for (i in 0..arr.size - 1) { arr[i] = arr[i] * pi / (pi * i + 1) } //arr.forEachIndexed { i, bigDecimal -> println("$i, ${bigDecimal.toString()}")} } fun main(args: Array<String>) { val bigdecimalTime = Stopwatch.elapse(::tryBigDecimal) println("BigDecimal took $bigdecimalTime ms") val doubleTime = Stopwatch.elapse(::tryDouble) println("Double took $doubleTime ms") } 

Sí, BigDecimal es apropiado para dinero. O cualquier otra situación en la que necesite precisión en lugar de velocidad.

Punto flotante

Los types float , Float , double y Double usan tecnología de coma flotante .

El propósito del punto flotante es cambiar la precisión para la velocidad de ejecución. Por lo tanto, a menudo ves dígitos incorrectos al final de la fracción decimal. Esto es aceptable para juegos, visualizaciones 3D y muchas aplicaciones científicas. Las computadoras comúnmente tienen hardware especializado para acelerar los cálculos de coma flotante. Esto es posible porque el IEEE tiene un comportamiento de punto flotante estandarizado de manera concreta.

El punto flotante no es aceptable para transactions financieras . Tampoco es el punto flotante aceptable en cualquier otra situación que espera fracciones correctas.

BigDecimal

Los dos propósitos de BigDecimal son:

  • Maneje un número arbitrariamente grande / pequeño.
  • No usar tecnología de punto flotante.

Entonces, ¿qué necesita tu aplicación? Lento pero preciso? O, rápido pero poco preciso? Esas son tus elecciones. Las computadoras no son mágicas, las computadoras no son infinitamente rápidas ni infinitamente precisas. La progtwigción es como la ingeniería en que se trata de elegir entre concesiones según las necesidades de su aplicación particular.

BigDecimal es una de las funciones de durmiente más grandes en Java. Brillante trabajo de IBM y otros. No sé si alguna otra plataforma de desarrollo tiene una facilidad tan excelente para manejar con precisión los numbers decimales. Vea algunas presentaciones de JavaOne de años atrás si desea apreciar los problemas técnicos.

No inicialice un object BigDecimal pasando un flotante o doble:

 new BigDecimal( 1234.4321 ) // BAD - Do not do this. 

Ese argumento crea un valor float que introduce las imprecisiones de la tecnología de coma flotante. Usa los otros constructores.

 new BigDecimal( "1234.4321" ) // Good 

Puede probar Moneta , la implementación de reference JSR 354 (JavaMoney RI). Tiene una implementación de FastMoney :

FastMoney representa la representación numérica optimizada para la velocidad. Representa una cantidad monetaria solo como un número integral de tipo long , usando una escala numérica de 100 000 (10 ^ 5).

p.ej

 operator fun MonetaryAmount.times(multiplicand: Double): MonetaryAmount { return multiply(multiplicand) } operator fun MonetaryAmount.div(divisor: Double): MonetaryAmount { return divide(divisor) } fun tryFastMoney() { val currency = Monetary.getCurrency("USD") val arr: MutableList<MonetaryAmount> = arrayListOf() for (i in 1..10000000) { arr.add(FastMoney.of(i, currency)) } val pi = 3.14159 for (i in 0..arr.size - 1) { arr[i] = arr[i] * pi / (pi * i + 1) } } fun main(args: Array<String>) { val fastMoneyTime = Stopwatch.elapse(::tryFastMoney) println("FastMoney took $fastMoneyTime ms") val doubleTime = Stopwatch.elapse(::tryDouble) println("Double took $doubleTime ms") } 
 FastMoney took 7040 ms Double took 4319 ms 

La solución más común para las finanzas es usar Int o varios Int s:

 val pi = 314159 // the point is implicit. To get the real value multiply `pi * 0.00001` 

De esa manera, usted controla explícitamente todo sobre los numbers (ei los restantes después de una split).

Puede usar Long , pero no es atómico, y por lo tanto no es seguro al mismo time. Lo que significa que tienes que sincronizar en cualquier Long compartido que tengas.

Una regla de oro es no utilizar nunca aritmética de punto flotante (ei Double o Float ) para las finanzas, porque, bueno, su punto flota, lo que garantiza absolutamente nada cuando los numbers son grandes.

  • No se puede inicializar el analizador de objects para el model. Productos, no se encontraron constructores aceptables
  • Robolectric KeyStoreKeyGenerator
  • Tratando de entender el ejemplo de Kotlin
  • La label no denota un bucle en paraCada
  • Kotlin Spek - ¿Cómo generar XML con informe de testings?
  • Configuración de Kotlin para Gradle sin asistencia de IDE: las classs de Kotlin no llegan a classpath
  • No se puede get que SupportMapFragment devuelva nada ... parece ser siempre nulo
  • Kotlin: ¿Cómo puedo invocar un campo lambda que tiene un tipo genérico de su class?
  • ¿Cómo hago que IntelliJ respete el directory de salida del complemento Maven Kotlin?
  • ¿Cómo convierto un Int en una cadena en Kotlin?
  • reflexión kotlin get list de campos