Puzzel2:Time for a Change

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中那樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章