有没有办法让BigDecimal比我在这里更快?

时间:2016-10-07 15:45:49

标签: java kotlin bigdecimal

我正在研究一些需要处理大量(数据)数据的财务分析软件。我想使用BigDecimal来获得准确性(一些定价信息会在十进制的右边四位或五位),但我担心速度。

我编写了以下测试应用程序,看起来BigDecimal可能比双打慢90到100倍。我知道会有一个三角洲,但这比我想象的要多。这是许多试验后的典型输出。

BigDecimal took 17944 ms
Double took 181 ms

我错过了什么吗?

这是代码。我试图让它代表现实世界。我创建了一个常量,我可以(pi),但也做了一些数据的内联数学,这些数字会从数据行变为数据行 - 例如pi * BigDecimal(i)+ BigDecimal(1)。我的观点是避免构造者不是唯一的答案。

幸运的是,看起来Double有足够的精度,因为数字的格式通常为00000.00000。但是,我应该知道任何隐藏的陷阱?人们使用Double进行财务分析软件吗?

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

3 个答案:

答案 0 :(得分:2)

您可以尝试Moneta,即JSR 354参考实现(JavaMoney RI)。它有一个FastMoney实现:

  

FastMoney表示针对速度进行了优化的数字表示。它仅将货币金额表示为long类型的整数,因此使用100&#39;(10 ^ 5)的数字刻度。

e.g。

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

答案 1 :(得分:2)

是的,BigDecimal适合赚钱。或者你需要准确而不是速度的任何其他情况。

浮点

floatFloatdoubleDouble类型都使用floating-point技术。

浮点的目的是以准确性换取执行速度。因此,您经常会在小数部分的末尾看到无关的错误数字。这对于游戏,3D可视化和许多科学应用是可接受的。计算机通常具有专用硬件来加速浮点计算。这是可能的,因为IEEE具体标准化了浮点行为。

浮动点可接受金融交易。在任何其他需要正确分数的情况下,浮点也不可接受。

BigDecimal

BigDecimal的两个目的是:

  • 处理任意大小的数字。
  • 不使用浮点技术。

那么,您的应用需要什么?慢但准确吗?或者,快速但稍微不准确?这些是你的选择。计算机不是魔术,计算机不是无限快,也不是无限精确。编程就像工程一样,只需根据特定应用的需要在权衡之间进行选择。

BigDecimal是Java中最大的睡眠功能之一。 IBM和其他人的出色工作。我不知道是否有任何其他开发平台具有如此出色的精确处理十进制数的工具。如果您想了解技术问题,请参阅多年前的一些JavaOne演示文稿。

通过传递float或double来初始化BigDecimal对象:

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

该参数创建float值,引入浮点技术的不准确性。使用其他构造函数。

new BigDecimal( "1234.4321" )  // Good

答案 2 :(得分:0)

最常见的财务解决方案是使用Int或多个Int

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

这样你就明确地控制了关于数字的一切(例如,除法后的剩余部分)。

您可以使用Long,但它不是原子的,因此它不是同时安全的。这意味着您必须在您拥有的任何共享Long上进行同步。

经验法则是永远不要使用浮点算术(e.i。DoubleFloat)来获得财务,因为,它的点浮动,因此当数字很大时绝对没有任何保证。