百分比計算看似簡單,實際開發中常遇到整數除法截斷、浮點數精度誤差、格式化需求(小數位數、百分號、千分位)等問題。本文提供生產級工具方法,涵蓋基本計算、精度控制、格式化輸出與邊界處理。
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
public class PercentUtils {
private static final DecimalFormat DEFAULT_FMT = new DecimalFormat("##.00%");
private static final DecimalFormat PCT_FMT_2 = new DecimalFormat("0.00%");
private static final DecimalFormat PCT_FMT_4 = new DecimalFormat("0.0000%");
/**
* 計算百分比並格式化(預設保留兩位小數,含百分號)
* @param numerator 分子
* @param denominator 分母
* @return 格式化字串,如 "49.15%"
* @throws ArithmeticException 分母為零
*/
public static String formatPercent(long numerator, long denominator) {
if (denominator == 0) throw new ArithmeticException("分母不能為零");
double ratio = (double) numerator / denominator;
return DEFAULT_FMT.format(ratio);
}
/**
* 高精度計算(金融/統計場景),使用 BigDecimal
* @param numerator 分子
* @param denominator 分母
* @param scale 保留小數位數
* @param rounding 舍入模式
* @return BigDecimal 比率(0~1 之間),未乘 100
*/
public static BigDecimal calculateRatio(BigDecimal numerator, BigDecimal denominator,
int scale, RoundingMode rounding) {
if (denominator.compareTo(BigDecimal.ZERO) == 0)
throw new ArithmeticException("分母不能為零");
return numerator.divide(denominator, scale, rounding);
}
/**
* 格式化 BigDecimal 比率為百分比字串
* @param ratio 0~1 之間的比率
* @param scale 百分比小數位數
* @return 如 "49.1523%"
*/
public static String formatRatio(BigDecimal ratio, int scale) {
DecimalFormat fmt = new DecimalFormat("0." + "0".repeat(scale) + "%");
// ratio * 100
return fmt.format(ratio.multiply(BigDecimal.valueOf(100)));
}
/** 便利方法:long 參數,指定小數位 */
public static String formatPercent(long num, long den, int scale) {
if (den == 0) throw new ArithmeticException("分母不能為零");
DecimalFormat fmt = new DecimalFormat("0." + "0".repeat(scale) + "%");
double ratio = (double) num / den;
return fmt.format(ratio);
}
/** 便利方法:double 參數 */
public static String formatPercent(double ratio, int scale) {
DecimalFormat fmt = new DecimalFormat("0." + "0".repeat(scale) + "%");
return fmt.format(ratio);
}
}
public class PercentDemo {
public static void main(String[] args) {
// 1. 基本用法
System.out.println(PercentUtils.formatPercent(29, 59)); // 49.15%
System.out.println(PercentUtils.formatPercent(1, 3, 4)); // 33.3333%
System.out.println(PercentUtils.formatPercent(0.12345, 2)); // 12.35%
// 2. 高精度金融計算
BigDecimal num = new BigDecimal("12345.67");
BigDecimal den = new BigDecimal("98765.43");
BigDecimal ratio = PercentUtils.calculateRatio(num, den, 6, RoundingMode.HALF_UP);
System.out.println("Ratio: " + ratio); // 0.124999
System.out.println("Percent: " + PercentUtils.formatRatio(ratio, 4)); // 12.4999%
// 3. 邊界測試
try { PercentUtils.formatPercent(10, 0); }
catch (ArithmeticException e) { System.out.println("捕獲異常: " + e.getMessage()); }
// 4. 大數值
System.out.println(PercentUtils.formatPercent(123456789L, 987654321L, 2)); // 12.50%
}
}
| 問題 | 解決方案 |
|---|---|
| 整數相除截斷 | 先轉 double 或乘 1.0:num * 1.0 / den |
| 浮點數精度誤差(如 0.1+0.2) | 金融/統計場景改用 BigDecimal,字串建構避免二進位誤差 |
| 格式化小數位不固定 | DecimalFormat("0.00%") 強制兩位;"##.00%" 整數部分不補零 |
| 四捨五入模式需求 | BigDecimal.divide(scale, RoundingMode.HALF_UP) 等 8 種模式可選 |
| 分母為零 | 顯式檢查拋出 ArithmeticException,避免 Infinity 污染下游 |
| 負數百分比顯示 | DecimalFormat 自動處理負號,格式如 -12.50% |
0 - 數字,不足位補 0
# - 數字,不足位不補
. - 小數點
% - 乘 100 並顯示百分號
, - 千分位分隔符
; - 分隔正負數模式(如 "0.00%;-0.00%")
DecimalFormat 非執行緒安全,不要 作為 static 共用實例於多執行緒環境。改用 ThreadLocal 或每次新建(輕量)。String.format("%.2f%%", ratio * 100),但精度控制較弱。BigDecimal 物件創建開銷較大,非金融場景優先用 double + DecimalFormat。百分比計算的核心在於精度選型(double vs BigDecimal)與格式化模式(DecimalFormat)。封裝通用工具類並覆蓋邊界測試,可消除專案中散落的重複邏輯與潛在 Bug,提升程式碼可維護性。