- 1 1. Giới thiệu
- 2 2. BigDecimal là gì?
- 3 3. Cách sử dụng cơ bản của BigDecimal
- 4 4. Sử dụng nâng cao của BigDecimal
- 5 5. Các lỗi thường gặp và cách khắc phục
- 6 6. Ví Dụ Sử Dụng Thực Tế
- 7 7. Summary
- 8 8. FAQ: Frequently Asked Questions About BigDecimal
- 8.1 Q1. Why should I use BigDecimal instead of float or double?
- 8.2 Q2. What is the safest way to construct BigDecimal instances?
- 8.3 Q3. Why does divide() throw an exception?
- 8.4 Q4. What’s the difference between compareTo() and equals()?
- 8.5 Q5. How do I perform rounding?
- 8.6 Q6. Can I check decimal digits (scale)?
- 8.7 Q7. How should I handle null/empty input safely?
1. Giới thiệu
Vấn đề độ chính xác trong tính toán số học bằng Java
Trong lập trình Java, các phép tính số học được thực hiện hàng ngày. Ví dụ, tính giá sản phẩm, xác định thuế hoặc lãi suất — những thao tác này cần thiết trong rất nhiều ứng dụng. Tuy nhiên, khi các phép tính này được thực hiện bằng các kiểu số thực như float hoặc double, lỗi không mong muốn có thể xuất hiện.
Điều này xảy ra vì float và double biểu diễn giá trị dưới dạng xấp xỉ nhị phân. Các giá trị như “0.1” hay “0.2”, có thể biểu diễn chính xác trong hệ thập phân, lại không thể biểu diễn một cách chính xác trong hệ nhị phân — và do đó, các sai lệch nhỏ sẽ tích lũy.
BigDecimal là cần thiết cho các phép tính tiền tệ hoặc độ chính xác cao
Những sai lệch này có thể trở nên nghiêm trọng trong các lĩnh vực như tính toán tiền tệ và các phép tính khoa học/kỹ thuật yêu cầu độ chính xác. Ví dụ, trong tính toán hoá đơn, chỉ một chênh lệch 1-yên cũng có thể gây ra vấn đề uy tín.
Đây là nơi lớp BigDecimal của Java tỏa sáng. BigDecimal có thể xử lý các số thập phân với độ chính xác tùy ý và khi thay thế float hoặc double bằng nó, các phép tính số học có thể được thực hiện mà không gặp lỗi.
Những gì bạn sẽ nhận được từ bài viết này
Trong bài viết này, chúng tôi sẽ giải thích các kiến thức cơ bản về cách sử dụng BigDecimal trong Java, các kỹ thuật nâng cao, cũng như các lỗi thường gặp và lưu ý cần tránh một cách có hệ thống.
Điều này hữu ích cho những ai muốn thực hiện các phép tính tiền tệ một cách chính xác trong Java hoặc đang cân nhắc áp dụng BigDecimal vào dự án của mình.
2. BigDecimal là gì?
Tổng quan về BigDecimal
BigDecimal là một lớp trong Java cho phép thực hiện các phép tính thập phân có độ chính xác cao. Nó thuộc gói java.math và được thiết kế đặc biệt cho các phép tính không chấp nhận lỗi như tính toán tài chính/kế toán/thuế.
Với float và double của Java, các giá trị số được lưu trữ dưới dạng xấp xỉ nhị phân — nghĩa là các số thập phân như “0.1” hay “0.2” không thể biểu diễn một cách chính xác, và đây chính là nguồn gốc của lỗi. Ngược lại, BigDecimal lưu trữ giá trị dưới dạng chuỗi thập phân, do đó giảm thiểu lỗi làm tròn và xấp xỉ.
Xử lý các số có độ chính xác tùy ý
Đặc điểm lớn nhất của BigDecimal là “độ chính xác tùy ý”. Cả phần nguyên và phần thập phân về lý thuyết đều có thể xử lý số chữ số gần như không giới hạn, tránh việc làm tròn hoặc mất chữ số do giới hạn độ dài.
Ví dụ, số lớn sau có thể được xử lý một cách chính xác:
BigDecimal bigValue = new BigDecimal("12345678901234567890.12345678901234567890");
Khả năng thực hiện các phép tính đồng thời giữ nguyên độ chính xác như vậy là sức mạnh chính của BigDecimal.
Các trường hợp sử dụng chính
BigDecimal được khuyến nghị trong các tình huống như:
- Tính toán tiền tệ — tính lãi, tính thuế trong các ứng dụng tài chính
- Xử lý số tiền hoá đơn / báo giá
- Các phép tính khoa học/kỹ thuật yêu cầu độ chính xác cao
- Các quy trình mà việc tích lũy lâu dài gây ra lỗi tích tụ
Ví dụ, trong hệ thống kế toán và tính lương — nơi một chênh lệch 1-yên có thể dẫn đến tổn thất lớn hoặc tranh chấp — độ chính xác của BigDecimal là thiết yếu.
3. Cách sử dụng cơ bản của BigDecimal
Cách tạo các đối tượng BigDecimal
Khác với các literal số thông thường, BigDecimal thường nên được khởi tạo từ một chuỗi. Điều này vì các giá trị được tạo từ double hoặc float có thể đã chứa sẵn lỗi xấp xỉ nhị phân.
Khuyến nghị (khởi tạo từ String):
BigDecimal value = new BigDecimal("0.1");
Tránh (khởi tạo từ double):
BigDecimal value = new BigDecimal(0.1); // may contain error
Cách thực hiện các phép toán
BigDecimal không thể dùng với các toán tử số học thông thường (+, -, *, /). Thay vào đó, phải sử dụng các phương thức chuyên biệt.
Cộng (add)
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.3");
BigDecimal result = a.add(b); // 12.8
Trừ (subtract)
BigDecimal result = a.subtract(b); // 8.2
Nhân (multiply)
BigDecimal result = a.multiply(b); // 24.15
Phép chia (divide) và Chế độ làm tròn
Phép chia cần thận trọng. Nếu không chia hết, ArithmeticException sẽ xảy ra trừ khi chỉ định chế độ làm tròn.
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 3.33
Ở đây chúng ta chỉ định “2 chữ số thập phân” và “làm tròn lên nửa”.
Đặt Scale và Chế độ làm tròn với setScale
setScale có thể được dùng để làm tròn tới một số chữ số nhất định.
BigDecimal value = new Big BigDecimal("123.456789");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46
Các giá trị RoundingMode phổ biến:
| Mode Name | Description |
|---|---|
HALF_UP | Round half up (standard rounding) |
HALF_DOWN | Round half down |
HALF_EVEN | Banker’s rounding |
UP | Always round up |
DOWN | Always round down |
BigDecimal là bất biến
BigDecimal là bất biến. Nghĩa là — các phương thức số học (add, subtract, v.v.) không thay đổi giá trị gốc — chúng trả về một thể hiện mới.
BigDecimal original = new BigDecimal("5.0");
BigDecimal result = original.add(new BigDecimal("1.0"));
System.out.println(original); // still 5.0
System.out.println(result); // 6.0
4. Sử dụng nâng cao của BigDecimal
So sánh giá trị: Sự khác nhau giữa compareTo và equals
Trong BigDecimal, có hai cách để so sánh giá trị: compareTo() và equals(), và chúng hoạt động khác nhau.
compareTo()chỉ so sánh giá trị số (bỏ qua scale).equals()so sánh kèm scale (số chữ số thập phân).BigDecimal a = new BigDecimal("10.0"); BigDecimal b = new BigDecimal("10.00"); System.out.println(a.compareTo(b)); // 0 (values are equal) System.out.println(a.equals(b)); // false (scale differs)
Lưu ý: Đối với kiểm tra bằng nhau về số học — như bằng nhau tiền tệ — compareTo() thường được khuyến nghị.
Chuyển đổi từ/đến String
Trong nhập liệu người dùng và nhập khẩu tệp bên ngoài, việc chuyển đổi với kiểu String là phổ biến.
String → BigDecimal
BigDecimal value = new Big BigDecimal("1234.56");
BigDecimal → String
String str = value.toString(); // "1234.56"
Sử dụng valueOf
Java cũng có BigDecimal.valueOf(double val), nhưng nó cũng bên trong chứa lỗi của double, vì vậy việc tạo từ chuỗi vẫn an toàn hơn.
BigDecimal unsafe = BigDecimal.valueOf(0.1); // contains internal error
Độ chính xác và quy tắc làm tròn qua MathContext
MathContext cho phép bạn kiểm soát độ chính xác và chế độ làm tròn cùng lúc — hữu ích khi áp dụng các quy tắc chung cho nhiều phép tính.
MathContext mc = new MathContext(4, RoundingMode.HALF_UP);
BigDecimal result = new BigDecimal("123.4567").round(mc); // 123.5
Cũng có thể dùng trong phép tính:
BigDecimal a = new BigDecimal("10.456");
BigDecimal b = new BigDecimal("2.1");
BigDecimal result = a.multiply(b, mc); // 4-digit precision
Kiểm tra null và Khởi tạo an toàn
Các biểu mẫu có thể truyền giá trị null hoặc rỗng — mã bảo vệ là tiêu chuẩn.
String input = ""; // empty
BigDecimal value = (input == null || input.isEmpty()) ? BigDecimal.ZERO : new BigDecimal(input);
Kiểm tra Scale của BigDecimal
Để biết số chữ số thập phân, dùng scale():
BigDecimal value = new BigDecimal("123.45");
System.out.println(value.scale()); // 3
5. Các lỗi thường gặp và cách khắc phục
ArithmeticException: Mở rộng thập phân không kết thúc
Ví dụ lỗi:
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.divide(b); // exception
Đây là “1 ÷ 3” — vì nó tạo ra một số thập phân không kết thúc, nếu không chỉ định chế độ làm tròn/scale, một ngoại lệ sẽ được ném.
Cách khắc phục: chỉ định scale + chế độ làm tròn
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // OK (3.33)
Lỗi khi tạo trực tiếp từ double
Passing a double directly may contain binary error already — producing unexpected values.
Ví dụ không tốt:
BigDecimal val = new BigDecimal(0.1);
System.out.println(val); // 0.100000000000000005551115123...
Đúng: Sử dụng String
BigDecimal val = new BigDecimal("0.1"); // exact 0.1
Lưu ý: BigDecimal.valueOf(0.1) sử dụng Double.toString() nội bộ, vì vậy nó “gần như giống” new BigDecimal("0.1") — nhưng chuỗi là an toàn 100%.

Hiểu lầm về equals do không khớp Scale
Vì equals() so sánh scale, nó có thể trả về false ngay cả khi các giá trị về mặt số học là bằng nhau.
BigDecimal a = new BigDecimal("10.0");
BigDecimal b = new BigDecimal("10.00");
System.out.println(a.equals(b)); // false
Giải pháp: sử dụng compareTo() để so sánh số học
System.out.println(a.compareTo(b)); // 0
Kết quả không mong đợi do độ chính xác không đủ
Nếu sử dụng setScale mà không chỉ định chế độ làm tròn — có thể xảy ra ngoại lệ.
Ví dụ không tốt:
BigDecimal value = new BigDecimal("1.2567");
BigDecimal rounded = value.setScale(2); // exception
Giải pháp:
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // OK
NumberFormatException Khi Giá Trị Đầu Vào Không Hợp Lệ
Nếu văn bản không hợp lệ không thể phân tích thành số được truyền vào (ví dụ: đầu vào người dùng / trường CSV), sẽ xảy ra NumberFormatException.
Giải pháp: sử dụng xử lý ngoại lệ
try {
BigDecimal value = new BigDecimal(userInput);
} catch (NumberFormatException e) {
// show error message or fallback logic
}
6. Ví Dụ Sử Dụng Thực Tế
Ở đây chúng tôi giới thiệu các kịch bản thực tế minh họa cách BigDecimal có thể được sử dụng trong thực tế. Đặc biệt trong các tính toán tài chính/kế toán/thuế, tầm quan trọng của việc xử lý số chính xác trở nên rõ ràng.
Xử lý số thập phân trong tính toán giá (Làm tròn phần thập phân)
Ví dụ: Tính giá bao gồm thuế tiêu thụ 10%
BigDecimal price = new BigDecimal("980"); // price w/o tax
BigDecimal taxRate = new BigDecimal("0.10");
BigDecimal tax = price.multiply(taxRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal total = price.add(tax);
System.out.println("Tax: " + tax); // Tax: 98
System.out.println("Total: " + total); // Total: 1078
Điểm chú ý:
- Kết quả tính thuế thường được xử lý dưới dạng số nguyên, sử dụng
setScale(0, RoundingMode.HALF_UP)để làm tròn. doublethường gây ra lỗi — nên dùngBigDecimal.
Tính toán giảm giá (% OFF)
Ví dụ: Giảm giá 20%
BigDecimal originalPrice = new BigDecimal("3500");
BigDecimal discountRate = new BigDecimal("0.20");
BigDecimal discount = originalPrice.multiply(discountRate).setScale(0, RoundingMode.HALF_UP);
BigDecimal discountedPrice = originalPrice.subtract(discount);
System.out.println("Discount: " + discount); // Discount: 700
System.out.println("After discount: " + discountedPrice); // 2800
Điểm chú ý: Các phép tính giảm giá không được mất độ chính xác.
Tính toán Đơn Giá × Số Lượng (Kịch bản Ứng dụng Kinh doanh Điển hình)
Ví dụ: 298.5 yên × 7 món
BigDecimal unitPrice = new BigDecimal("298.5");
BigDecimal quantity = new BigDecimal("7");
BigDecimal total = unitPrice.multiply(quantity).setScale(2, RoundingMode.HALF_UP);
System.out.println("Total: " + total); // 2089.50
Điểm chú ý:
- Điều chỉnh làm tròn cho phép nhân có phần thập phân.
- Quan trọng cho hệ thống kế toán / đặt hàng.
Tính lãi suất kép (Ví dụ tài chính)
Ví dụ: Lãi suất hàng năm 3% × 5 năm
BigDecimal principal = new BigDecimal("1000000"); // base: 1,000,000
BigDecimal rate = new BigDecimal("0.03");
int years = 5;
BigDecimal finalAmount = principal;
for (int i = 0; i < years; i++) {
finalAmount = finalAmount.multiply(rate.add(BigDecimal.ONE)).setScale(2, RoundingMode.HALF_UP);
}
System.out.println("Sau 5 năm: " + finalAmount); // khoảng 1,159,274.41
Point:
- Repeated calculations accumulate errors — BigDecimal avoids this.
Validation & Conversion of User Input
public static BigDecimal parseAmount(String input) {
try {
return new BigDecimal(input).setScale(2, RoundingMode.HALF_UP);
} catch (NumberFormatException e) {
return BigDecimal.ZERO; // coi đầu vào không hợp lệ là 0
}
}
Points:
- Safely convert user-provided numeric strings.
- Validation + error fallback improves robustness.
7. Summary
The Role of BigDecimal
In Java’s numeric processing — especially monetary or precision-required logic — the BigDecimal class is indispensable. Errors inherent in float / double can be dramatically avoided by using BigDecimal.
This article covered fundamentals, arithmetic, comparisons, rounding, error handling, and real-world examples.
Key Review Points
BigDecimalhandles arbitrary-precision decimal — ideal for money and precision math- Initialization should be via string literal , e.g.
new BigDecimal("0.1") - Use
add(),subtract(),multiply(),divide(), and always specify rounding mode when dividing - Use
compareTo()for equality — understand difference vsequals() setScale()/MathContextlet you finely control scale + rounding- Real business logic cases include money, tax, quantity × unit price etc.
For Those About to Use BigDecimal
Although “handling numbers in Java” looks simple — precision / rounding / numeric error problems always exist behind it. BigDecimal is a tool that directly addresses those problems — mastering it lets you write more reliable code.
At first you may struggle with rounding modes — but with real project usage, it becomes natural.
Next chapter is an FAQ section summarizing common questions about BigDecimal — useful for review and specific semantic searches.
8. FAQ: Frequently Asked Questions About BigDecimal
Q1. Why should I use BigDecimal instead of float or double?
A1.
Because float/double represent numbers as binary approximations — decimal fractions cannot be represented exactly. This causes results such as “0.1 + 0.2 ≠ 0.3.”
BigDecimal preserves decimal values exactly — ideal for money or precision-critical logic.
Q2. What is the safest way to construct BigDecimal instances?
A2.
Always construct from string.
Bad (error):
new BigDecimal(0.1)
Correct:
new BigDecimal("0.1")
BigDecimal.valueOf(0.1) uses Double.toString() internally, so it’s almost same — but string is the safest.
Q3. Why does divide() throw an exception?
A3.
Because BigDecimal.divide() throws ArithmeticException when result is a non-terminating decimal.
Solution: specify scale + rounding mode
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
Q4. What’s the difference between compareTo() and equals()?
A4.
compareTo()checks numeric equality (scale ignored)equals()checks exact equality including scalenew BigDecimal("10.0").compareTo(new BigDecimal("10.00")); // → 0 new BigDecimal("10.0").equals(new BigDecimal("10.00")); // → false
Q5. How do I perform rounding?
A5.
Use setScale() with explicit rounding mode.
BigDecimal value = new BigDecimal("123.4567");
BigDecimal rounded = value.setScale(2, RoundingMode.HALF_UP); // 123.46
Main rounding modes:
RoundingMode.HALF_UP(round half up)RoundingMode.DOWN(round down)RoundingMode.UP(round up)
Q6. Can I check decimal digits (scale)?
A6.
Yes — use scale().
BigDecimal val = new BigDecimal("123.45");
System.out.println(val.scale()); // → 3
Q7. How should I handle null/empty input safely?
A7.
Luôn bao gồm kiểm tra null + xử lý ngoại lệ.
public static BigDecimal parseSafe(String input) {
if (input == null || input.trim().isEmpty()) return BigDecimal.ZERO;
try {
return new BigDecimal(input.trim());
} catch (NumberFormatException e) {
return BigDecimal.ZERO;
}
}

