代码之家  ›  专栏  ›  技术社区  ›  tpdi

加倍我的钱:我的框架使用加倍的货币量

  •  9
  • tpdi  · 技术社区  · 15 年前

    我继承了一个项目,其中货币金额使用双精度类型。

    更糟糕的是,它使用的框架,以及框架本身的类,使用double来赚钱。

    框架ORM还处理从数据库(和存储到数据库)检索值的操作。在数据库中,money值是type number(19,7),但是框架ORM将它们映射为doubles。

    除了完全绕过框架类和ORM,我能做些什么来精确计算货币价值吗?

    编辑:是的,我知道应该用大十进制。问题是我与一个框架紧密相连,在这个框架中,例如framework.commerce.pricing框架.ItemPriceInfo有两个成员mRawTotalPrice;和两个mListPrice。我公司的应用程序自己的代码扩展了,例如,这个ItemPriceInfoClass。

    实际上,我不能对我的公司说,“因为舍入错误,放弃两年的工作,花费数十万美元,基于这个框架的代码”

    5 回复  |  直到 15 年前
        1
  •  10
  •   Michael Petrotta user3140870    15 年前

    如果可以接受,则将货币类型视为整数。换句话说,如果你在美国工作,跟踪美分而不是美元,如果美分提供你需要的粒度。双精度数可以精确地表示 a very large value (2^53) (没有舍入到该值的误差)。

    但实际上,正确的做法是完全绕过框架,使用更合理的方法。对于框架来说,这是一个业余的错误-谁知道还隐藏着什么?

        2
  •  6
  •   Robert Greiner    15 年前

    我没看到你提到重构。我想这是你最好的选择。与其花点时间把事情搞得更好,为什么不用正确的方法来解决呢?

    以下是一些关于 double vs BigDecimal . 这篇文章建议使用 BigDecimal 尽管速度比较慢。

        3
  •  4
  •   Peter Lawrey    15 年前

    很多人会建议使用BigDecimal,如果你不知道如何在项目中使用舍入,那就是你应该做的。

    如果你知道如何正确使用小数取整,请使用double。它的许多数量级更快、更清晰、更简单,因而不易出错。如果你使用美元和美分(或者需要两个小数位),你可以得到一个高达70万亿美元的精确结果。

    基本上,如果使用适当的舍入进行更正,就不会出现舍入错误。

    顺便说一句:舍入错误的想法让许多开发人员感到恐惧,但事实并非如此 随机的 你可以很容易地处理错误。

    编辑:考虑这个简单的舍入误差的例子。

        double a = 100000000.01;
        double b = 100000000.09;
        System.out.println(a+b); // prints 2.0000000010000002E8
    

    有许多可能的舍入策略。您可以在打印/显示时舍入结果。例如

        System.out.printf("%.2f%n", a+b); // prints 200000000.10
    

    或者用数学方法把结果四舍五入

        double c = a + b;
        double r= (double)((long)(c * 100 + 0.5))/100;
        System.out.println(r); // prints 2.000000001E8
    

    在我的例子中,当从服务器发送(写入套接字和文件)时,我对结果进行舍入,但是使用我自己的例程来避免任何对象创建。

    更一般的round函数如下,但如果您可以使用printf或DecimalFormat,则可以更简单。

    private static long TENS[] = new long[19]; static { 
        TENS[0] = 1; 
        for (int i = 1; i < TENS.length; i++) TENS[i] = 10 * TENS[i - 1]; 
    } 
    
    public static double round(double v, int precision) { 
        assert precision >= 0 && precision < TENS.length; 
        double unscaled = v * TENS[precision]; 
        assert unscaled > Long.MIN_VALUE && unscaled < Long.MAX_VALUE; 
        long unscaledLong = (long) (unscaled + (v < 0 ? -0.5 : 0.5)); 
        return (double) unscaledLong / TENS[precision]; 
    }
    

    注意:您可以使用BigDecimal来执行最后的舍入。如果你需要一个指定的round方法。

        4
  •  1
  •   vstoyanov    15 年前

    实际上你没有那么多选择:

    您可以重构项目以使用BigDecimal(或更适合其需要的东西)来表示金钱。

    要特别小心溢出/下溢和精度损失,这意味着要增加大量的检查,并以不必要的方式重构更大比例的系统。更不用说如果你要这么做的话需要多少研究。

    保持现状,希望没人注意到(这是个笑话)。

    IMHO,最好的解决方案是简单地重构它。这可能是一些沉重的重构,但邪恶已经完成,我相信这应该是你最好的选择。

    最好的, 瓦西里

    P、 哦,你可以把钱当作整数(数美分),但如果你要进行货币兑换、计算利息等,这听起来不是个好主意。

        5
  •  0
  •   Community Mohan Dere    8 年前

    我认为这种情况至少对您的代码是最小限度的补救。您可以通过ORM框架以double的形式获得该值。然后可以使用staticvalueof方法将其转换为BigDecimal(请参见 here 在对其进行任何数学/计算之前,然后将其转换回仅用于存储的double。

    因为您无论如何都在扩展这些类,所以可以为您的double值添加getter,以便在需要时将它们作为BigDecimal获取。

    这可能不能覆盖100%的情况(我会特别担心ORM或JDBC驱动程序将double转换回数字类型所做的工作),但这比仅仅对原始double进行计算要好得多。

    然而,从长远来看,我还远不能确信这种方法实际上对公司来说更便宜。