为什么不使用 Double 或 Float 来表示货币?

发布于 2024-09-19 06:11:27 字数 119 浏览 2 评论 0原文

我总是被告知永远不要doublefloat类型来表示金钱,这次我向你提出问题:为什么?

我确信有一个很好的理由,我只是不知道它是什么。

I've always been told never to represent money with double or float types, and this time I pose the question to you: why?

I'm sure there is a very good reason, I simply do not know what it is.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

你另情深 2024-09-26 06:11:27

因为浮点数和双精度数无法准确表示我们用于货币的以 10 为基数的倍数。这个问题不仅适用于 Java,也适用于任何使用 2 进制浮点类型的编程语言。

以 10 为底,您可以将 10.25 写为 1025 * 10-2(整数乘以 10 的幂)。 IEEE-754 浮点数 是不同的,但考虑它们的一个非常简单的方法是而是乘以 2 的幂。例如,您可能会看到 164 * 2-4(整数乘以 2 的幂),它也等于 10.25。这不是数字在内存中的表示方式,但数学含义是相同的。

即使以 10 为基数,此表示法也无法准确表示大多数简单分数。例如,您无法表示 1/3:十进制表示是重复的 (0.3333...),因此不存在可以乘以 10 的幂得到 1/3 的有限整数。您可以选择一长串 3 和一个小指数,例如 333333333 * 10-10,但这并不准确:如果将其乘以 3,则不会得到 1。

然而,为了数钱的目的,至少对于货币价值在美元一个数量级以内的国家来说,通常你所需要的只是能够存储 10-2 的倍数,所以无法表示 1/3 并不重要。

浮点数和双精度数的问题在于,绝大多数类似货币的数字都没有精确表示为整数乘以 2 的幂。事实上,0 和 0 之间唯一的 0.01 倍数1(在处理金钱时很重要,因为它们是整数分)可以精确表示为 IEEE-754 二进制浮点数是 0、0.25、0.5、0.75 和 1。所有其他值都相差一个小值数量。与 0.333333 示例进行类比,如果将浮点值设为 0.01,并将其乘以 10,则不会得到 0.1。相反,您会得到类似 0.099999999786 的结果...

将金钱表示为 doublefloat 起初可能看起来不错,因为软件会消除微小的错误,但当您执行时对不精确的数字进行更多的加法、减法、乘法和除法,错误将会复合,最终会得到明显不准确的值。这使得浮点数和双精度数不足以处理金钱,因为金钱需要以 10 为底的幂倍数的完美精度。

几乎适用于任何语言的解决方案是使用整数,并计算分。例如,1025 就是 10.25 美元。几种语言还具有处理金钱的内置类型。其中,Java 有 BigDecimal 类,Rust 有 rust_decimal 箱子,而 C# 具有 decimal 类型。

Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn't just for Java, it's for any programming language that uses base 2 floating-point types.

In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10). IEEE-754 floating-point numbers are different, but a very simple way to think about them is to multiply by a power of two instead. For instance, you could be looking at 164 * 2-4 (an integer times a power of two), which is also equal to 10.25. That's not how the numbers are represented in memory, but the math implications are the same.

Even in base 10, this notation cannot accurately represent most simple fractions. For instance, you can't represent 1/3: the decimal representation is repeating (0.3333...), so there is no finite integer that you can multiply by a power of 10 to get 1/3. You could settle on a long sequence of 3's and a small exponent, like 333333333 * 10-10, but it is not accurate: if you multiply that by 3, you won't get 1.

However, for the purpose of counting money, at least for countries whose money is valued within an order of magnitude of the US dollar, usually all you need is to be able to store multiples of 10-2, so it doesn't really matter that 1/3 can't be represented.

The problem with floats and doubles is that the vast majority of money-like numbers don't have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they're integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won't get 0.1. Instead you will get something like 0.099999999786...

Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, errors will compound and you'll end up with values that are visibly not accurate. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.

A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. Among others, Java has the BigDecimal class, and Rust has the rust_decimal crate, and C# has the decimal type.

尐籹人 2024-09-26 06:11:27

来自 Bloch, J.,《Effective Java》(第二版,第 48 项。第三版,第 60 项):

floatdouble 类型是
特别不适合货币
计算,因为这是不可能的
代表 0.1(或任何其他
10 的负次方)作为 float
精确地加倍

例如,假设您有 1.03 美元
你花了 42c。多少钱办
你走了吗?

System.out.println(1.03 - .42);

打印出0.6100000000000001

解决这个问题的正确方法是
使用 BigDecimalintlong
用于货币计算。

尽管 BigDecimal 有一些警告(请参阅当前接受的答案)。

From Bloch, J., Effective Java, (2nd ed, Item 48. 3rd ed, Item 60):

The float and double types are
particularly ill-suited for monetary
calculations because it is impossible
to represent 0.1 (or any other
negative power of ten) as a float or
double exactly.

For example, suppose you have $1.03
and you spend 42c. How much money do
you have left?

System.out.println(1.03 - .42);

prints out 0.6100000000000001.

The right way to solve this problem is
to use BigDecimal, int or long
for monetary calculations.

Though BigDecimal has some caveats (please see currently accepted answer).

雅心素梦 2024-09-26 06:11:27

这不是准确度的问题,也不是精密度的问题。这是一个满足人类使用基数 10 进行计算而不是基数 2 的期望的问题。例如,使用双精度数进行金融计算不会产生数学意义上的“错误”答案,但它可以产生以下答案:这不是财务意义上的预期。

即使您在输出前的最后一刻对结果进行四舍五入,您仍然偶尔会使用双打得到与预期不符的结果。

使用计算器,或者手工计算结果,1.40 * 165 = 231 正好。但是,在我的编译器/操作系统环境中内部使用双精度数,它存储为接近 230.99999 的二进制数...因此,如果截断该数字,则会得到 230 而不是 231。您可能会认为舍入而不是截断会给出了所需的结果 231。这是事实,但四舍五入总是涉及截断。无论您使用什么舍入技术,仍然存在像这样的边界条件,当您期望它向上舍入时,它会向下舍入。它们非常罕见,通常不会通过偶然的测试或观察发现。您可能需要编写一些代码来搜索说明结果未按预期运行的示例。

假设您想将某项四舍五入到最接近的便士。因此,您将最终结果乘以 100,再加上 0.5,截断,然后将结果除以 100,即可得到便士。如果您存储的内部数字是 3.46499999...而不是 3.465,那么当您将数字四舍五入到最接近的美分时,您将得到 3.46 而不是 3.47。但您以 10 为基数的计算结果可能表明答案应该是 3.465,显然应该向上舍入为 3.47,而不是向下舍入为 3.46。当您使用双精度数进行财务计算时,这种情况在现实生活中偶尔会发生。这种情况很少见,因此经常被忽视作为一个问题,但它确实发生了。

如果您使用基数 10 而不是双精度数进行内部计算,那么假设您的代码中没有其他错误,答案总是完全符合人类的预期。

This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2. For example, using doubles for financial calculations does not produce answers that are "wrong" in a mathematical sense, but it can produce answers that are not what is expected in a financial sense.

Even if you round off your results at the last minute before output, you can still occasionally get a result using doubles that does not match expectations.

Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999... so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.

Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999.... instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.

If you use base 10 for your internal calculations instead of doubles, the answers are always exactly what is expected by humans, assuming no other bugs in your code.

帅气称霸 2024-09-26 06:11:27

我对其中一些回应感到困扰。我认为双精度数和浮点数在财务计算中占有一席之地。当然,在添加和减去非小数货币金额时,使用整数类或 BigDecimal 类不会损失精度。但是,在执行更复杂的运算时,无论如何存储数字,结果通常会超出几个或多个小数位。问题在于你如何呈现结果。

如果您的结果处于向上舍入和向下舍入之间的边界,并且最后一分钱确实很重要,那么您可能应该通过显示更多的小数位来告诉观众答案几乎在中间。

双精度数(尤其是浮点数)的问题在于它们用于组合大数和小数时。在java中,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

结果为

1.1875

I'm troubled by some of these responses. I think doubles and floats have a place in financial calculations. Certainly, when adding and subtracting non-fractional monetary amounts there will be no loss of precision when using integer classes or BigDecimal classes. But when performing more complex operations, you often end up with results that go out several or many decimal places, no matter how you store the numbers. The issue is how you present the result.

If your result is on the borderline between being rounded up and rounded down, and that last penny really matters, you should be probably be telling the viewer that the answer is nearly in the middle - by displaying more decimal places.

The problem with doubles, and more so with floats, is when they are used to combine large numbers and small numbers. In java,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

results in

1.1875
傲鸠 2024-09-26 06:11:27

我冒着被否决的风险,但我认为浮点数不适合货币计算被高估了。只要您确保正确进行分位数舍入,并有足够的有效数字来处理 zneak 解释的二进制十进制表示不匹配,就不会有问题。

在 Excel 中使用货币进行计算的人们一直使用双精度浮点数(Excel 中没有货币类型),而且我还没有看到有人抱怨舍入错误。

当然,你必须保持理智;例如,一个简单的网上商店可能永远不会遇到双精度浮点数的任何问题,但如果您进行会计或其他需要添加大量(不受限制)数字的操作,您不会希望用十英尺来接触浮点数极。

I'll risk being downvoted, but I think the unsuitability of floating point numbers for currency calculations is overrated. As long as you make sure you do the cent-rounding correctly and have enough significant digits to work with in order to counter the binary-decimal representation mismatch explained by zneak, there will be no problem.

People calculating with currency in Excel have always used double precision floats (there is no currency type in Excel) and I have yet to see anyone complaining about rounding errors.

Of course, you have to stay within reason; e.g. a simple webshop would probably never experience any problem with double precision floats, but if you do e.g. accounting or anything else that requires adding a large (unrestricted) amount of numbers, you wouldn't want to touch floating point numbers with a ten foot pole.

独孤求败 2024-09-26 06:11:27

浮点数和双精度数是近似值。如果您创建一个 BigDecimal 并将一个浮点数传递到构造函数中,您会看到该浮点数实际等于什么:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

这可能不是您想要表示 $1.01 的方式。

问题是 IEEE 规范没有办法精确表示所有分数,其中一些分数最终会成为重复分数,因此最终会出现近似错误。由于会计师喜欢事情精确到一分钱,如果客户支付了账单,但在付款处理后他们欠 0.01 并且他们被收取费用或无法关闭他们的帐户,他们会很恼火,所以最好使用精确类型,如十进制(C# 中)或 Java 中的 java.math.BigDecimal。

如果您舍入,并不是说错误无法控制: 请参阅此彼得·劳瑞 (Peter Lawrey) 的文章。一开始就不必四舍五入会更容易。大多数处理金钱的应用程序不需要大量数学运算,操作包括添加内容或将金额分配到不同的存储桶。引入浮点和舍入只会使事情变得复杂。

Floats and doubles are approximate. If you create a BigDecimal and pass a float into the constructor you see what the float actually equals:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

this probably isn't how you want to represent $1.01.

The problem is that the IEEE spec doesn't have a way to exactly represent all fractions, some of them end up as repeating fractions so you end up with approximation errors. Since accountants like things to come out exactly to the penny, and customers will be annoyed if they pay their bill and after the payment is processed they owe .01 and they get charged a fee or can't close their account, it's better to use exact types like decimal (in C#) or java.math.BigDecimal in Java.

It's not that the error isn't controllable if you round: see this article by Peter Lawrey. It's just easier not to have to round in the first place. Most applications that handle money don't call for a lot of math, the operations consist of adding things or allocating amounts to different buckets. Introducing floating point and rounding just complicates things.

娇俏 2024-09-26 06:11:27

虽然浮点类型确实只能表示近似十进制数据,但如果在呈现数字之前将数字四舍五入到必要的精度,则可以获得正确的结果。通常。

通常是因为double类型的精度小于16位。如果您需要更高的精度,那么它不适合。近似值也可以累积。

必须指出的是,即使您使用定点运算,您仍然必须对数字进行舍入,如果您获得周期十进制数时 BigInteger 和 BigDecimal 不会给出错误的话。所以这里也有一个近似值。

例如,历史上用于金融计算的 COBOL 的最大精度为 18 位数字。因此经常会出现隐式舍入。

最后,我认为 double 不适合主要是因为它的 16 位精度,这可能是不够的,而不是因为它是近似的。

考虑后续程序的以下输出。它表明,在舍入 double 后,得到与 BigDecimal 相同的结果,最高精度为 16。

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}

While it's true that floating point type can represent only approximatively decimal data, it's also true that if one rounds numbers to the necessary precision before presenting them, one obtains the correct result. Usually.

Usually because the double type has a precision less than 16 figures. If you require better precision it's not a suitable type. Also approximations can accumulate.

It must be said that even if you use fixed point arithmetic you still have to round numbers, were it not for the fact that BigInteger and BigDecimal give errors if you obtain periodic decimal numbers. So there is an approximation also here.

For example COBOL, historically used for financial calculations, has a maximum precision of 18 figures. So there is often an implicit rounding.

Concluding, in my opinion the double is unsuitable mostly for its 16 digit precision, which can be insufficient, not because it is approximate.

Consider the following output of the subsequent program. It shows that after rounding double give the same result as BigDecimal up to precision 16.

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}
情释 2024-09-26 06:11:27

浮点数的结果并不精确,这使得它们不适合任何需要精确结果而不是近似值的金融计算。 float 和 double 是为工程和科学计算而设计的,很多时候不会产生精确的结果,而且浮点计算的结果可能会因 JVM 的不同而有所不同。看下面用于表示货币价值的 BigDecimal 和 double 基元的示例,很明显浮点计算可能不准确,应该使用 BigDecimal 进行金融计算。

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

输出:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

Output:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
倚栏听风 2024-09-26 06:11:27

正如之前所说,“用双精度或浮点数表示金钱一开始可能看起来不错,因为软件会四舍五入微小的错误,但是当您对不精确的数字执行更多的加法、减法、乘法和除法时,您将失去越来越多的精度随着误差的增加,浮点数和双精度数不足以处理金钱,而金钱的处理需要以 10 为底的幂的倍数的完美精度。”

Java 终于有了一个使用货币和货币的标准方法!

JSR 354:货币和货币 API

JSR 354 提供了一个 API,用于表示、传输和执行货币的综合计算和货币。您可以从此链接下载:

JSR 354:货币和货币 API 下载

< strong>该规范由以下内容组成:

  1. 用于处理金额和货币等内容的 API
  2. 支持可互换实现的 API
  3. 用于创建实现类实例的工厂
  4. 货币金额计算、转换和格式化功能
  5. 用于处理货币和货币的 Java API,计划包含在 Java 9 中。
  6. 所有规范类和接口都位于 javax.money.* 包中。

JSR 354 的示例:货币和货币 API:

创建 MonetaryAmount 并将其打印到控制台的示例如下所示:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

使用参考实现 API 时,必要的代码要简单得多:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

API还支持使用 MonetaryAmounts 进行计算:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit 和 MonetaryAmount

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount 有多种方法允许访问指定的货币、数字金额、其精度等:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;

MonetaryAmounts 可以使用舍入运算符进行舍入:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

在处理 MonetaryAmounts 集合时,有一些不错的实用方法可以进行过滤、排序和分组。

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

自定义 MonetaryAmount 操作

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
    BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
    BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
    return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

资源:

使用 JSR 354 在 Java 中处理金钱和货币

深入了解 Java 9货币和货币 API (JSR 354)

另请参阅:JSR 354 - 货币和货币

As said earlier "Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you’ll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required."

Finally Java has a standard way to work with Currency And Money!

JSR 354: Money and Currency API

JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. You can download it from this link:

JSR 354: Money and Currency API Download

The specification consists of the following things:

  1. An API for handling e. g. monetary amounts and currencies
  2. APIs to support interchangeable implementations
  3. Factories for creating instances of the implementation classes
  4. Functionality for calculations, conversion and formatting of monetary amounts
  5. Java API for working with Money and Currencies, which is planned to be included in Java 9.
  6. All specification classes and interfaces are located in the javax.money.* package.

Sample Examples of JSR 354: Money and Currency API:

An example of creating a MonetaryAmount and printing it to the console looks like this:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

When using the reference implementation API, the necessary code is much simpler:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

The API also supports calculations with MonetaryAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit and MonetaryAmount

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount has various methods that allow accessing the assigned currency, the numeric amount, its precision and more:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;

MonetaryAmounts can be rounded using a rounding operator:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

When working with collections of MonetaryAmounts, some nice utility methods for filtering, sorting and grouping are available.

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

Custom MonetaryAmount operations

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
    BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
    BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
    return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

Resources:

Handling money and currencies in Java with JSR 354

Looking into the Java 9 Money and Currency API (JSR 354)

See Also: JSR 354 - Currency and Money

蓝天白云 2024-09-26 06:11:27

大多数答案都强调了为什么不应使用双精度数进行货币和货币计算的原因。我完全同意他们的观点。

但这并不意味着双打永远不能用于此目的。

我参与过许多 gc 要求非常低的项目,而 BigDecimal 对象是造成这种开销的一个重要原因。

正是由于缺乏对双重表示的理解以及缺乏处理准确性和精确性的经验,才产生了这个明智的建议。

如果您能够处理项目的精度和准确度要求,则可以使其发挥作用,这必须根据要处理的双精度值的范围来完成。

您可以参考 guava 的 FuzzyCompare 方法来了解更多信息。参数容差是关键。
我们针对证券交易应用程序处理了这个问题,并对不同范围内的不同数值使用什么容差进行了详尽的研究。

此外,在某些情况下,您可能会想使用双包装器作为映射键,并以哈希映射作为实现。这是非常危险的,因为 Double.equals 和哈希码例如值“0.5”和“0.5”。 “0.6 - 0.1”会造成很大的混乱。

Most answers have highlighted the reasons why one should not use doubles for money and currency calculations. And I totally agree with them.

It doesn't mean though that doubles can never be used for that purpose.

I have worked on a number of projects with very low gc requirements, and having BigDecimal objects was a big contributor to that overhead.

It's the lack of understanding about double representation and lack of experience in handling the accuracy and precision that brings about this wise suggestion.

You can make it work if you are able to handle the precision and accuracy requirements of your project, which has to be done based on what range of double values is one dealing with.

You can refer to guava's FuzzyCompare method to get more idea. The parameter tolerance is the key.
We dealt with this problem for a securities trading application and we did an exhaustive research on what tolerances to use for different numerical values in different ranges.

Also, there might be situations when you're tempted to use Double wrappers as a map key with hash map being the implementation. It is very risky because Double.equals and hash code for example values "0.5" & "0.6 - 0.1" will cause a big mess.

阳光①夏 2024-09-26 06:11:27

如果您的计算涉及多个步骤,任意精度算术将无法 100% 覆盖您。

使用结果的完美表示的唯一可靠方法(使用自定义分数数据类型将批量除法运算到最后一步)并且仅在最后一步中转换为十进制表示法。

任意精度无济于事,因为总是可能存在具有如此多小数位的数字,或者某些结果,例如 0.6666666...没有任意表示形式可以覆盖最后一个示例。所以每一步都会有小错误。

这些错误会累积起来,最终可能变得不再容易被忽视。这称为错误传播

If your computation involves various steps, arbitrary precision arithmetic won't cover you 100%.

The only reliable way to use a perfect representation of results(Use a custom Fraction data type that will batch division operations to the last step) and only convert to decimal notation in the last step.

Arbitrary precision won't help because there always can be numbers that has so many decimal places, or some results such as 0.6666666... No arbitrary representation will cover the last example. So you will have small errors in each step.

These errors will add-up, may eventually become not easy to ignore anymore. This is called Error Propagation.

您的好友蓝忘机已上羡 2024-09-26 06:11:27

此问题的许多答案都讨论了 IEEE 以及围绕浮点运算的标准。

来自非计算机科学背景(物理学和工程学),我倾向于从不同的角度看待问题。对我来说,我不会在数学计算中使用双精度或浮点数的原因是我会丢失太多信息。

有哪些替代方案?有很多(还有更多我不知道的!)。

Java 中的 BigDecimal 是 Java 语言原生的。
Apfloat 是另一个 Java 任意精度库。

C# 中的十进制数据类型是 Microsoft 的 .NET 替代方案,表示 28 位有效数字。

SciPy(科学Python)可能还可以处理金融计算(我没有尝试过,但我怀疑是这样)。

GNU 多精度库 (GMP) 和 GNU MFPR 库是 C 和 C++ 的两个免费开源资源。

还有 JavaScript 的数值精度库(!),我认为 PHP 可以处理金融计算。

还有针对许多计算机语言的专有解决方案(我认为尤其是 Fortran)和开源解决方案。

我不是受过训练的计算机科学家。然而,我倾向于 Java 中的 BigDecimal 或 C# 中的十进制。我还没有尝试过我列出的其他解决方案,但它们可能也非常好。

对我来说,我喜欢 BigDecimal 因为它支持的方法。 C# 的小数非常好,但我还没有机会像我希望的那样使用它。我在业余时间做我感兴趣的科学计算,BigDecimal 似乎工作得很好,因为我可以设置浮点数的精度。 BigDecimal 的缺点?有时它可能会很慢,特别是当您使用除法时。

为了提高速度,您可以查看 C、C++ 和 Fortran 中的免费和专有库。

Many of the answers posted to this question discuss IEEE and the standards surrounding floating-point arithmetic.

Coming from a non-computer science background (physics and engineering), I tend to look at problems from a different perspective. For me, the reason why I wouldn't use a double or float in a mathematical calculation is that I would lose too much information.

What are the alternatives? There are many (and many more of which I am not aware!).

BigDecimal in Java is native to the Java language.
Apfloat is another arbitrary-precision library for Java.

The decimal data type in C# is Microsoft's .NET alternative for 28 significant figures.

SciPy (Scientific Python) can probably also handle financial calculations (I haven't tried, but I suspect so).

The GNU Multiple Precision Library (GMP) and the GNU MFPR Library are two free and open-source resources for C and C++.

There are also numerical precision libraries for JavaScript(!) and I think PHP which can handle financial calculations.

There are also proprietary (particularly, I think, for Fortran) and open-source solutions as well for many computer languages.

I'm not a computer scientist by training. However, I tend to lean towards either BigDecimal in Java or decimal in C#. I haven't tried the other solutions I've listed, but they are probably very good as well.

For me, I like BigDecimal because of the methods it supports. C#'s decimal is very nice, but I haven't had the chance to work with it as much as I'd like. I do scientific calculations of interest to me in my spare time, and BigDecimal seems to work very well because I can set the precision of my floating point numbers. The disadvantage to BigDecimal? It can be slow at times, especially if you're using the divide method.

You might, for speed, look into the free and proprietary libraries in C, C++, and Fortran.

夏末的微笑 2024-09-26 06:11:27

为了补充之前的答案,在处理问题中解决的问题时,除了 BigDecimal 之外,还可以选择在 Java 中实现 Joda-Money 。 Java 模块名称是 org.joda.money。

它需要 Java SE 8 或更高版本,并且没有依赖项。

更准确地说,存在编译时依赖,但事实并非如此
必填。

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

使用 Joda Money 的示例:

  // create a monetary value
  Money money = Money.parse("USD 23.87");
  
  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));
  
  // subtracts an amount in dollars
  money = money.minusMajor(2);
  
  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);
  
  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);
  
  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
  
  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

文档:
http://joda-money.sourceforge.net/apidocs/ org/joda/money/Money.html

实现示例:
https://www.programcreek.com/java -api-examples/?api=org.joda.money.Money

To add on previous answers, there is also the option of implementing Joda-Money in Java, besides BigDecimal, when dealing with the problem addressed in the question. Java module name is org.joda.money.

It requires Java SE 8 or later and has no dependencies.

To be more precise, there is a compile-time dependency but it is not
required.

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

Examples of using Joda Money:

  // create a monetary value
  Money money = Money.parse("USD 23.87");
  
  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));
  
  // subtracts an amount in dollars
  money = money.minusMajor(2);
  
  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);
  
  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);
  
  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
  
  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

Documentation:
http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html

Implementation examples:
https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money

廻憶裏菂餘溫 2024-09-26 06:11:27

看一下这个简单的例子:它看起来逻辑上是正确的,但在现实世界中,如果没有正确地受到威胁,这可能会返回意外的结果:

0.1 x 10 = 1

Take a look at this simple example: it looks like logically correct, but in real world this can return unexpected results if not threated correctly:

0.1 x 10 = 1 ???? , so:

double total = 0.0;

// adds 10 cents, 10 times
for (int i = 0; i < 10; i++) {
    total += 0.1;  // adds 10 cents
}

Log.d("result: ", "current total: " + total);

// looks like total equals to 1.0, don't?

// now, do reverse
for (int i = 0; i < 10; i++) {
    total -= 0.1;  // removes 10 cents
}

// total should be equals to 0.0, right?
Log.d("result: ", "current total: " + total);
if (total == 0.0) {
    Log.d("result: ", "is total equal to ZERO? YES, of course!!");
} else {
    Log.d("result: ", "is total equal to ZERO? No...");
    // so be careful comparing equality in this cases!!!
}

OUTPUT:

 result: current total: 0.9999999999999999
 result: current total: 2.7755575615628914E-17   ????
 result: is total equal to ZERO? No... ????
铜锣湾横着走 2024-09-26 06:11:27

Float 是 Decimal 的二进制形式,但设计不同;它们是两个不同的东西。两种类型相互转换时几乎没有误差。此外,浮点数被设计用来表示无限大的科学值。这意味着它被设计为在固定字节数的情况下丢失极小和极大数字的精度。小数不能表示无限数量的值,它仅限于小数位数。所以Float和Decimal有不同的目的。

有一些方法可以管理货币值的错误:

  1. 使用长整数并以分为单位计数。

  2. 使用双精度,将有效数字保留为 15,以便可以精确模拟小数。在呈现值之前舍入;计算时经常舍入。

  3. 使用像Java BigDecimal这样的十进制库,这样你就不需要使用double来模拟十进制。

PS 有趣的是,大多数品牌的手持式科学计算器都使用十进制而不是浮点数。所以没有人抱怨浮点转换错误。

Float is binary form of Decimal with different design; they are two different things. There are little errors between two types when converted to each other. Also, float is designed to represent infinite large number of values for scientific. That means it is designed to lost precision to extreme small and extreme large number with that fixed number of bytes. Decimal can't represent infinite number of values, it bounds to just that number of decimal digits. So Float and Decimal are for different purpose.

There are some ways to manage the error for currency value:

  1. Use long integer and count in cents instead.

  2. Use double precision, keep your significant digits to 15 only so decimal can be exactly simulated. Round before presenting values; Round often when doing calculations.

  3. Use a decimal library like Java BigDecimal so you don't need to use double to simulate decimal.

p.s. it is interesting to know that most brands of handheld scientific calculators works on decimal instead of float. So no one complaint float conversion errors.

沫离伤花 2024-09-26 06:11:27

美国货币可以很容易地用美元和美分来表示。整数是 100% 精确的,而浮点二进制数与浮点小数并不完全匹配。

American currency can easily be represented with dollar and cent amounts. Integers are 100% precise, while floating point binary numbers do not exactly match floating point decimals.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文