Consider the following word problem:
考虑以下word problem:
Tom goes to the auto parts store to buy a spark plug that costs $1.10, but all he has in his wallet are two-dollar bills. How much change should he get if he pays for the spark plug with a two-dollar bill?
Tom去汽车配件店买一个价格为 $1.10的火花塞,但他口袋里只有一张两美元的钞票。如果他用这些钱买火花塞会得到多少找零?
Here is a program that attempts to solve the word problem. What does it print?
这里是一个企图解决此问题的程序,它会打印出什么?
public class Change { public static void main(String args[]) { System.out.println(2.00 - 1.10); } }
Solution 2: Time for a Change
Naively, you might expect the program to print 0.90, but how could it know that you wanted two digits after the decimal point? If you know something about the rules for converting double values to strings, which are specified by the documentation for Double.toString [Java-API], you know that the program prints the shortest decimal fraction sufficient to distinguish the double value from its nearest neighbor, with at least one digit before and after the decimal point. It seems reasonable, then, that the program should print 0.9. Reasonable, perhaps, but not correct. If you ran the program, you found that it prints 0.8999999999999999.
你也许会天真地期待程序打印出 0.90,但是程序怎么知道你希望得到小数点后两位数?如果你知道一些double型转换为string型的规则,在java-api文档里有详细的double.tostring说明[Java-API],,你了解到程序打印出最短十进制片段,这个数字片段足够与其最近的邻居区别开来,并且在小数点前后各有至少一位。看上去很合理,那么程序应当打印出0.9。合理的,可能是,但不正确。如果你跑这段程序,你发现它打印出0.8999999999999999。
The problem is that the number 1.1 can't be represented exactly as a double, so it is represented by the closest double value. The program subtracts this value from 2. Unfortunately, the result of this calculation is not the closest double value to 0.9. The shortest representation of the resulting double value is the hideous number that you see printed.
问题其实是数字1.1作为double型数据不能被精确表现,所以它其实是被一个最近似的double值来表现。程序从2减去这个值。计算结果不幸不是0.9的最近似值。结果最短表现值是你看到的令人惊讶的数字。
More generally, the problem is that not all decimals can be represented exactly using binary floating-point. If you are using release 5.0 or a later release, you might be tempted to fix the program by using the printf facility to set the precision of the output:
更一般的,问题是用二进制浮点不能精确表现所有十进制数。如果你用了5.0或以上的版本,你可以用print facility来修正程序设置它的输出精度
// Poor solution - still uses binary floating-point!
//差劲的解法-仍然使用二进制浮点
System.out.printf("%.2f%n", 2.00 - 1.10);
This prints the right answer but does not represent a general solution to the underlying problem: It still uses double arithmetic, which is binary floating-point. Floating-point arithmetic provides good approximations over a wide range of values but does not generally yield exact results. Binary floating-point is particularly ill-suited to monetary calculations, as it is impossible to represent 0.1—or any other negative power of 10—exactly as a finite-length binary fraction [EJ Item 31].
这将打印正确的结果但并未对根本问题有一般的解决;它仍然用二进制浮点的double运算。浮点运算提供了广泛的好的近似但并不会一般性的得出精确的结果。二进制浮点尤其不合适货币计算,因为不可能将0.1或其它任何10的负数次方数精确表现为有限长度的二进制片段。[EJ Item 31].
One way to solve the problem is to use an integral type, such as int or long, and to perform the computation in cents. If you go this route, make sure the integral type is large enough to represent all the values you will use in your program. For this puzzle, int is ample. Here is how the println looks if we rewrite it using int values to represent monetary values in cents. This version prints 90 cents, which is the right answer:
解决此问题的一个方法是用整型数据,如int或long,且用cents执行计算。如果你这么做,确保整型足够大来表现所有你将在程序中用到的值。这个谜题中,int足够了。下面是如何使用int值来表现cents的数值来重新写程序,用println看结果。这个版本打印出90 cents,这是个正确的结果。
System.out.println((200 - 110) + " cents");
Another way to solve the problem is to use BigDecimal, which performs exact decimal arithmetic. It also interoperates with the SQL DECIMAL type via JDBC. There is one caveat: Always use the BigDecimal(String) constructor, never BigDecimal(double). The latter constructor creates an instance with the exact value of its argument: new BigDecimal(.1) returns a BigDecimal representing 0.1000000000000000055511151231257827021181583404541015625. Using BigDecimal correctly, the program
另一方法是用bigdecimal,它执行精确十进制运算。它通过jdbc与sql的decimal类型互相操作。有一个附加说明:总是用bigdecimal(string),绝不用bigdecimal(double)。后者创建一个其参数的精确实例;new BigDecimal(.1)返回一个BigDecimal表现为:0.1000000000000000055511151231257827021181583404541015625。正确使用BigDecimal,程序为:
prints the expected result of 0.90:
import java.math.BigDecimal; public class Change { public static void main(String args[]) { System.out.println(new BigDecimal("2.00"). subtract(new BigDecimal("1.10"))); } }
This version is not terribly pretty, as Java provides no linguistic support for BigDecimal. Calculations with BigDecimal are also likely to be slower than those with any primitive type, which might be an issue for some programs that make heavy use of decimal calculations. It is of no consequence for most programs.
这个版本不是非常漂亮,因为java对BigDecimal提供no linguistic的支持。BigDeimal的计算都比那些使用原始数据类型的要慢,这也许会是某些程序大量使用decimal计算时的问题。这是大多数程序的重点。
In summary, avoid float and double where exact answers are required; for monetary calculations, use int, long, or BigDecimal. For language designers, consider providing linguistic support for decimal arithmetic. One approach is to offer limited support for operator overloading, so that arithmetic operators can be made to work with numerical reference types, such as BigDecimal. Another approach is to provide a primitive decimal type, as did COBOL and PL/I.
总而言之,需要精确结果时避免float与double;对于货币计算,用int,long或BigDecimal。对于语言设计者,考虑对BigDecimal运算提供linguistic支持。一个方法是提供对操作符重载提供有限支持,这样一来运算符能够被用来与数字相关类型,如BigDecimal。另一方法是提供十进制数的原始数据类型,就如同COBOL和PL/I中那样。