Posted by: piman007 | 10-01-2008

double vs. BigDecimal

พอดีได้ไปอ่าน Blog น่ารู้เกี่ยวกับเรื่อง data type ของ Java มาครับ เค้าได้กล่าวถึงข้อผิดพลาดของ data type ที่เป็น floating-point ของ Java ไม่ว่าจะเป็น float หรือ double ก็ตาม เวลานำมาใช้คำนวณจริงๆ แล้วจะเกิดข้อผิดพลาดขึ้นได้ตลอดเวลา ถ้าเราไม่เข้าใจ อาจจะทำให้เราหา bug ใน program ไม่เจอได้ ก็เลยอยากเอามา share ให้เพื่อนๆ พี่ๆ น้องๆ ที่ทำงานอยู่ในสายนี้ ลองอ่านทำความเข้าใจดูครับ มีประโยชน์มากๆ

เพื่อความเข้าใจง่ายๆ ลอง เอา source code อันนี้ไป run ดูครับ

public static void main (String[] args) {
    System.out.println (
        "(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = " +
        (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1));

    double d = 0.0;
    while (d <= 1.0) d += 0.1;
    System.out.println ("d = " + d);

    System.out.println ("0.0175 * 100000 = " + 0.0175 * 100000);
}

ดูจาก source code ทุกท่านอาจจะคิดว่า output จะออกมาแบบนี้นะครับ

(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = 1
d = 1
0.0175 * 100000 = 1750.0

แต่จริงๆแล้ว output ออกมาเป็นแบบนี้ครับ

(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1) = 0.9999999999999999
d = 1.0999999999999999
0.0175 * 100000 = 1750.0000000000002

เห็นมั้ยครับ ว่ามันผิดถนัดเลย ไม่ถูกเลยซักค่า ยิ่งถ้าเราเอาไปใช้ใน program ที่เป็นการคำนวณด้านการเงินแล้วล่ะก็ เจ๊งแน่ๆ ครับ คนเขียน Blog เค้าเลยสรุป (ซึ่งผมก็เห็นด้วย) ไว้ดังนี้ครับ

  1. อย่าใช้ float หรือ double ในการคำนวณตัวเลขทศนิยมใน Java เด็ดขาด ให้ใช้ BigDecimal แทนครับ
  2. อย่าใช้ = หรือ != ในการเปรียบเทียบค่าตัวเลขทศนิยมครับ เพราะมันมีโอกาสเป็นไปได้ที่ค่ามันไม่เท่ากับที่เราพิมพ์จริงๆ แต่ให้แปลงค่าโดยใช้ Float.floatToIntBits (float) หรือ Double.doubleToLongBits (double) ก่อนการเปรียบเทียบครับ จะได้ความแม่นยำมากกว่า
  3. ทุกครั้งที่มีการประกาศตัวแปรเพื่อเก็บค่าเลขทศนิยม ให้ประกาศเป็น BigDecimal ครับ แล้วเวลาจะเก็บค่าลง database ค่อยไป convert เป็น float หรือ double อีกที

นอกจากเค้ามี code ตัวอย่างสาธิตการ add ด้วย BigDecimal มาให้ดูด้วย ดังนี้ครับ

// default to read a double primitive value of 18 digit
// precision
public static final NumberFormat DEFAULT_DECIMAL_FORMAT =
    new DecimalFormat ("#.0#################");
public static final BigDecimal ZERO = new BigDecimal ("0");

public static BigDecimal add (double a, double b) {
    String s = DEFAULT_DECIMAL_FORMAT.format(a);
    BigDecimal bd = new BigDecimal (s);
    return add (bd, b);
}

public static BigDecimal add (BigDecimal a, double b) {
    String s = DEFAULT_DECIMAL_FORMAT.format(b);
    BigDecimal bd = new BigDecimal (s);
    return add (a, bd);
}

public static BigDecimal add (BigDecimal a, BigDecimal b) {
    if (a == null) return (b == null) ? ZERO : b;
    return a.add (b);
}

เมื่อนำมา apply ใช้ใน code เราจะใช้แบบนี้ครับ

System.out.println (
    "add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1) = " +
    add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1));
System.out.println (
    "new BigDecimal ("0.0175").multiply (new BigDecimal ("100000").doubleValue()) = " +
    new BigDecimal ("0.0175").multiply (new BigDecimal ("100000")).doubleValue());

สุดท้ายเมื่อนำไป run แล้วจะได้ output เป็นแบบนี้

add (add (add (add (add (add (add (add (add (0.1, 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1), 0.1) = 1.0
new BigDecimal ("0.0175").multiply (new BigDecimal ("100000").doubleValue()) = 1750.0

ซึ่งถูกต้องและแม่นยำ 100% ครับ

ปล. มีคำแนะนำเพิ่มเติม ว่าอย่าใช้ constructor ของ BigDecimal ที่รับค่าเป็น double ครับ เพราะไม่ต่างจากการใช้ double เลย ให้ใช้ constructor ที่รับค่าเป็น String ครับ ดีที่สุด

Credits to http://epramono.blogspot.com/2005/01/double-vs-bigdecimal.html

คัดลอกจาก http://mylaruku.multiply.com/journal/item/13


ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

หมวดหมู่

%d bloggers like this: