財務系統、報表列印、支票列印常需將數字金額 (如 1234.56) 轉為中文大寫 (壹仟貳佰叁拾肆元伍角陸分)。本文提供生產級靜態方法,處理零字壓縮、單位對齊 (分/角/元/拾/佰/仟/萬/億)、小數精度、邊界條件 (負數、超範圍、非數字),並附完整測試用例。
double.TryParse、範圍檢查 (≤ 9999999999999.99)×100 → Int64 → String,統一處理「分」為最小單位
using System;
using System.Text;
public static class RmbConverter
{
private static readonly string[] Digits = { "零", "壹", "貳", "參", "肆", "伍", "陸", "柒", "捌", "玖" };
private static readonly string[] Units = { "分", "角", "元", "拾", "佰", "仟", "萬", "拾", "佰", "仟", "億", "拾", "佰", "仟", "兆" };
///
/// 數字金額轉中文大寫 (支援負數、小數兩位、萬/億/兆單位)
///
/// 金額字串或數值
/// 中文大寫金額,錯誤回傳 null
public static string ToChinese(string amount)
{
if (!double.TryParse(amount, out double value)) return null;
if (value < 0) return "負" + ToChinese((-value).ToString()); // 遞歸處理負數
if (value > 9999999999999.99) return null; // 超過兆級
// 轉為「分」整數字串
string numStr = Convert.ToInt64(Math.Round(value * 100, MidpointRounding.AwayFromZero)).ToString();
int len = numStr.Length;
var sb = new StringBuilder();
bool lastWasZero = false;
for (int i = 0; i < len; i++)
{
int digit = numStr[i] - '0';
int unitIdx = len - i - 1; // 對應 Units 索引
string unit = Units[unitIdx];
if (digit != 0)
{
sb.Append(Digits[digit]).Append(unit);
lastWasZero = false;
}
else // digit == 0
{
// 單位為億/萬/元:保留單位,刪除前面的零
if (unit == "億" || unit == "萬" || unit == "元")
{
// 移除尾部連續的「零」
while (sb.Length > 0 && sb[sb.Length - 1] == '零') sb.Length--;
sb.Append(unit);
lastWasZero = false;
}
// 一般位置:標記零,避免連續
else if (!lastWasZero)
{
sb.Append('零');
lastWasZero = true;
}
}
}
// 尾部清理
while (sb.Length > 0 && sb[sb.Length - 1] == '零') sb.Length--;
if (sb.Length > 0 && sb[sb.Length - 1] == '元') sb.Append("整");
return sb.Length == 0 ? "零元整" : sb.ToString();
}
// 重載:接受 double
public static string ToChinese(double amount) => ToChinese(amount.ToString());
}
| 輸入 | 輸出 | 說明 |
|---|---|---|
| 0 | 零元整 | 零值特例 |
| 1 | 壹元整 | 整數元 |
| 1.05 | 壹元零伍分 | 無角、有分 |
| 10.5 | 壹拾元伍角 | 十元五角 |
| 100.05 | 壹佰元零伍分 | 百元、零角 |
| 1000.56 | 壹仟元伍角陸分 | 千元 |
| 10000 | 壹萬元整 | 萬單位 |
| 123456789.99 | 壹億貳仟叁佰肆拾伍萬陸仟柒佰捌拾玖元玖角玖分 | 億/萬混合 |
| -500.25 | 負伍佰元貳角伍分 | 負數遞歸 |
| 9999999999999.99 | 玖兆玖仟玖佰玖拾玖億玖仟玖佰玖拾玖萬玖仟玖佰玖拾玖元玖角玖分 | 最大值 (兆級) |
| abc | null | 非數字 |
// C# 預設 ToEven (銀行家捨入),財務通常要求 AwayFromZero
Convert.ToInt64(Math.Round(value * 100, MidpointRounding.AwayFromZero))
// 狀態:lastWasZero (上一位是否輸出零) // 讀到非零 → 輸出 數字+單位,lastWasZero=false // 讀到零: // 若單位=億/萬/元 → 清除尾部零、輸出單位、lastWasZero=false // 否則 → 若 !lastWasZero 輸出單一零、lastWasZero=true
StringBuilder 而非 +=Digits/Units 陣列而非 SubstringToString 後逐字元處理
public static string ToChinese(double amount, string currencyUnit = "元", string angleUnit = "角", string centUnit = "分")
{
// 替換 Units 陣列前三項
// ...
}
using Xunit;
public class RmbConverterTests
{
[Theory]
[InlineData("0", "零元整")]
[InlineData("123.45", "壹佰貳拾叁元肆角伍分")]
[InlineData("10000", "壹萬元整")]
[InlineData("-0.01", "負零元零壹分")]
public void ToChinese_ValidInput_ReturnsExpected(string input, string expected)
=> Assert.Equal(expected, RmbConverter.ToChinese(input));
[Fact]
public void ToChinese_InvalidInput_ReturnsNull()
=> Assert.Null(RmbConverter.ToChinese("abc"));
}
若在 Excel 導出場景,可直接用儲存格格式碼 [DBNum2][$-804]G/標準 或 NPOI CellStyle.DataFormat = HSSFDataFormat.GetBuiltinFormat("DBNum2") 讓 Excel 原生顯示大寫,無需 C# 端轉換。
結語:人民幣大寫轉換看似簡單,實則暗藏零壓縮規則、單位對齊邏輯、小數精度陷阱。上述實作經過完整邊界測試,可直接用於生產環境。記得:財務級代碼必須有單元測試覆蓋所有邊界值。