Java มีหลายวิธีจัดการความแม่นยำของ floating-point numbers ขึ้นอยู่กับ Use Case
ปัญหาของ float/double
public class FloatPrecisionIssue {
public static void main(String[] args) {
System.out.println(0.1 + 0.2); // 0.30000000000000004 ❌
System.out.println(1.03 - 0.42); // 0.6100000000000001 ❌
// เพราะ binary floating-point ไม่สามารถ represent บางทศนิยมได้แม่นยำ
}
}
วิธีที่ 1: String.format() / printf - สำหรับ Display เท่านั้น
public class FormatExample {
public static void main(String[] args) {
double value = 34.909;
// Format to 7 decimal places
System.out.println(String.format("%.7f", value)); // 34.9090000
// printf
System.out.printf("Value: %.2f%n", 12.56); // 12.56
// จัดรูปแบบต่างๆ
System.out.printf("Scientific: %e%n", 1234.56); // 1.234560e+03
System.out.printf("General: %g%n", 1234.56); // 1234.56
System.out.printf("Hex float: %a%n", 12.56); // 0x1.98f5c28f5c28fp3
}
}
วิธีที่ 2: DecimalFormat - 靈活 Formatting
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
public class DecimalFormatExample {
public static void main(String[] args) {
double value = 1234567.89123;
// Pattern symbols: 0=digit, #=optional digit, .=decimal, ,=grouping
DecimalFormat df1 = new DecimalFormat("#,###.00");
System.out.println(df1.format(value)); // 1,234,567.89
DecimalFormat df2 = new DecimalFormat("0.00000");
System.out.println(df2.format(1.5)); // 1.50000
// Custom symbols (Locale)
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US);
symbols.setGroupingSeparator(',');
symbols.setDecimalSeparator('.');
DecimalFormat df3 = new DecimalFormat("#,##0.00", symbols);
System.out.println(df3.format(value)); // 1,234,567.89
// Currency
DecimalFormat currency = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.US);
System.out.println(currency.format(1234.56)); // $1,234.56
}
}
วิธีที่ 3: BigDecimal - Exact Precision (แนะนำสำหรับ Financial/Monetary)
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalExample {
public static void main(String[] args) {
// ❌ ผิด: BigDecimal(double) เก็บค่า binary approximation
BigDecimal bad = new BigDecimal(0.1);
System.out.println("Bad: " + bad); // 0.1000000000000000055511151231257827021181583404541015625
// ✅ ถูกต้อง: ใช้ String constructor หรือ valueOf
BigDecimal good1 = new BigDecimal("0.1");
BigDecimal good2 = BigDecimal.valueOf(0.1); // ใช้ double.toString() internal
System.out.println("Good: " + good1); // 0.1
// Arithmetic operations
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");
System.out.println("Add: " + a.add(b)); // 12.8
System.out.println("Subtract: " + a.subtract(b)); // 8.2
System.out.println("Multiply: " + a.multiply(b)); // 24.15
System.out.println("Divide: " + a.divide(b, 4, RoundingMode.HALF_UP)); // 4.5652
// Scale & Rounding
BigDecimal div = a.divide(b, 10, RoundingMode.HALF_UP); // 10 decimal places
System.out.println("Divide (10dp): " + div);
// setScale - ปรับ scale
BigDecimal scaled = div.setScale(2, RoundingMode.HALF_UP);
System.out.println("Scaled: " + scaled); // 4.57
// Compare
System.out.println("Compare: " + a.compareTo(b)); // 1 (a > b)
}
}
Rounding Modes
| Mode | คำอธิบาย | ตัวอย่าง (1.5 → ) |
| HALF_UP | ปัดขึ้นถ้า ≥ 0.5 (มาตรฐาน) | 2 |
| HALF_DOWN | ปัดขึ้นถ้า > 0.5 | 1 |
| HALF_EVEN | Banker's rounding (ปัดไปคู่) | 2 (1.5→2, 2.5→2) |
| UP | ปัดขึ้นจากศูนย์ | 2 |
| DOWN | ปัดลงไปศูนย์ | 1 |
| CEILING | ปัดขึ้นไป +∞ | 2 |
| FLOOR | ปัดลงไป -∞ | 1 |
| UNNECESSARY | ต้องหารลงตัว ไม่งั้น Exception | Error |
วิธีที่ 4: Math.round / StrictMath - Simple Rounding
public class MathRoundExample {
public static void main(String[] args) {
double d = 34.909;
// Round to integer
long rounded = Math.round(d); // 35
// Round to n decimal places (manual)
double rounded2 = Math.round(d * 100.0) / 100.0; // 34.91
double rounded3 = Math.round(d * 1000.0) / 1000.0; // 34.909
System.out.println(rounded2);
System.out.println(rounded3);
}
}
สรุป: เลือกวิธีไหนดี?
| Use Case | แนะนำ | เหตุผล |
| Financial/Money calculations | BigDecimal | Exact precision, ไม่มี floating-point errors |
| Scientific/Engineering calculations | double + Math.round/format | Performance, precision พอสำหรับ measurement |
| Display formatting (UI, Reports) | String.format / DecimalFormat | Flexible patterns, Locale support |
| Simple rounding to int | Math.round | Fast, simple |
| High precision math | BigDecimal / Apache Commons Math | Arbitrary precision |
Best Practices
- ❌ อย่าใช้
float/double สำหรับเงิน/การเงิน
- ✅ ใช้
BigDecimal พร้อม String constructor
- ✅ ระบุ
RoundingMode ทุกครั้งที่ divide/setScale
- ✅ ใช้
BigDecimal.valueOf(double) ถ้าต้องรับ double input
- ✅ เก็บเงินเป็น **สตางค์/เซนต์ (Integer)** ใน Database