將 Fortran 程式移植到 C,原程式在第 9 位小數截斷,C 語言用 pow 計算冪次出現第 14 位小數,第 8 位即產生進位誤差。程式被呼叫上萬次,要求精度「絲毫不差」。
float (單精度) 約 6-7 位十進位有效數字,double (雙精度) 約 15-16 位。pow、exp、log 等超越函數由標準庫實作,誤差通常在 0.5-1 ULP(最後一位單位),累積上萬次誤差不可控。| 方案 | 原理 | 精度 | 效能 | 實作複雜度 | 適用場景 |
|---|---|---|---|---|---|
| 定點整數運算 | 將數值乘固定倍率 (如 10^9) 轉為 int64_t 運算 | 絕對精確(無捨入) | 極快(整數運算) | 低 | 金融、貨幣、固定小數位科學計算 |
| 十進位浮點庫 | decNumber、libdfp (GCC)、_Decimal64/128 (C23/TR) | 可配置精度,十進位語意 | 較慢(軟體模擬) | 中 | 銀行、稅務、監管要求十進位精確 |
| 任意精度庫 | GMP (整數/有理數)、MPFR (浮點)、Boost.Multiprecision (C++) | 任意位數精確 | 慢(大數運算) | 高 | 密碼學、科學計算、驗證基準 |
| 自定義科學記數法結構 | 原回答提議:struct { long mantissa; long exp; } | 整數尾數精確,冪次分離 | 中等(需自實作四則運算) | 中 | 特定演算法、教學、輕量級 |
| 誤差分析與補償 | Kahan 求和、雙倍精度累積、誤差項追蹤 | 減輕累積誤差,非絕對精確 | 快 | 低 | 數值穩定性改善、科學計算 |
若業務需求為「固定小數位(如 9 位)精確計算」,直接用 int64_t 儲存「億分之一」單位:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#define SCALE 1000000000L // 10^9,對應 9 位小數
// 定點加法
int64_t fixed_add(int64_t a, int64_t b) { return a + b; }
// 定點減法
int64_t fixed_sub(int64_t a, int64_t b) { return a - b; }
// 定點乘法:結果需除以 SCALE,注意溢位與四捨五入
int64_t fixed_mul(int64_t a, int64_t b) {
__int128 tmp = (__int128)a * b; // GCC/Clang 支援 128 位中間值
return (int64_t)((tmp + SCALE/2) / SCALE); // 四捨五入
}
// 定點除法:被除數先乘 SCALE
int64_t fixed_div(int64_t a, int64_t b) {
__int128 tmp = (__int128)a * SCALE;
return (int64_t)((tmp + b/2) / b); // 四捨五入
}
// 從 double 轉換(僅初始化用)
int64_t from_double(double x) { return (int64_t)(x * SCALE + 0.5); }
// 輸出顯示
void print_fixed(int64_t x) {
int64_t int_part = x / SCALE;
int64_t frac_part = x % SCALE;
if (frac_part < 0) frac_part = -frac_part;
printf("%" PRId64 ".%09" PRId64 "\n", int_part, frac_part);
}
_Decimal64 (GCC/Clang 擴充,C23 標準化)
// 需 GCC/Clang,編譯加 -std=c23 或 -fdecimal-float
#include <stdio.h>
_Decimal64 a = 0.1dd; // 十進位字面常數後綴 dd
_Decimal64 b = 0.2dd;
_Decimal64 c = a + b; // 精確為 0.3,無二進位誤差
printf("%.20Df\n", c); // 輸出 0.30000000000000000000
// 需連結 -lgmp -lmpfr
#include <mpfr.h>
mpfr_t x, y, z;
mpfr_init2(x, 256); // 256 位精度
mpfr_init2(y, 256);
mpfr_init2(z, 256);
mpfr_set_str(x, "0.1", 10, MPFR_RNDN);
mpfr_set_str(y, "0.2", 10, MPFR_RNDN);
mpfr_add(z, x, y, MPFR_RNDN); // 精確十進位加法
mpfr_printf("%.50Rf\n", z); // 0.30000000000000000000000000000000000000000000000000
typedef struct MyFloat {
long a; // 尾數
long n; // 10 的冪次
} MyFloat;
long 範圍有限(通常 64 位),大數仍會溢位;效能遠遜原生浮點。double (雙精度) 或 long double (擴展精度),避免混用 float。pow:整數冪次用遞推乘法;小數冪次若精度敏感,考慮查表或多項式逼近(如 exp(y*log(x)))。
double kahan_sum(const double *arr, size_t n) {
double sum = 0.0, c = 0.0;
for (size_t i = 0; i < n; ++i) {
double y = arr[i] - c;
double t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum;
}
C 語言原生浮點無法保證十進位精度「絲毫不差」。工程上三大主流方案:
_Decimal64/128(標準化、語意正確、硬體支援中)。自定義結構可作教學或極輕量場景,生產環境請用成熟庫。