首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

J2ME CLDC 1.0 無浮點數環境下的定點數運算模擬技巧

     編輯:Java 相關知識

背景:CLDC 1.0 的浮點數缺失

  • **CLDC 1.0 (MIDP 1.0)**:**不支援 `float`、`double`** 基本型別
  • **Math 類極度精簡**:僅 `abs`、`min`/`max`、`sqrt` (整數版)、隨機數
  • **CLDC 1.1 (MIDP 2.0)** 才引入 `float`/`double` 完整支援
  • 大量存量功能機、嵌入式裝置仍運行 CLDC 1.0 VM

核心原理:定點數運算

**定點數** = 整數 × 固定縮放因子 (Scale Factor)

  • 選擇縮放因子 = 10^n (十進制) 或 2^n (二進制,位運算更快)
  • 所有運算在**整數域**完成,最終除以縮放因子得結果
  • 本質上是**手動管理小數點位置**

基礎範例:圓周長計算 (π ≈ 3.14)

方法一:縮放 100 倍 (十進制,直觀易懂)

// 需求:計算半徑 6 的圓周長 C = 2 * π * r
// π 放大 100 倍 = 314
int r = 6;
int pi_scaled = 314;  // 3.14 * 100
int scale = 100;

// 運算全在整數域
int circumference_scaled = 2 * r * pi_scaled;  // 2 * 6 * 314 = 3768
int circumference = circumference_scaled / scale;  // 3768 / 100 = 37 (截斷)

// 改進:四捨五入 (加半個 scale 再除)
int circumference_rounded = (circumference_scaled + scale / 2) / scale;  // (3768 + 50) / 100 = 38
// 真實值 37.68,四捨五入 38,誤差 0.32

方法二:縮放 256 倍 (二進制,位運算極快)

// 縮放因子 256 = 2^8,除法 = 右移 8 位
int SCALE = 256;
int SCALE_SHIFT = 8;

// π ≈ 3.14159 * 256 ≈ 804
int PI_FIXED = 804;

int r = 6;
// 2 * r * PI_FIXED = 2 * 6 * 804 = 9648
int result_fixed = (2 * r * PI_FIXED) >> SCALE_SHIFT;  // 9648 >> 8 = 37

// 四捨五入:加 128 (SCALE/2) 再右移
int result_rounded = (2 * r * PI_FIXED + (SCALE >> 1)) >> SCALE_SHIFT;  // (9648 + 128) >> 8 = 38

通用定點數工具類:FixedPointMath

public class FixedPointMath {
    // 縮放因子 1000 (支援 3 位小數,十進制友好)
    public static final int SCALE = 1000;
    public static final int HALF_SCALE = 500;
    
    // 常用常數 (已縮放)
    public static final int PI = 3142;        // 3.142
    public static final int PI_DIV_2 = 1571;  // 1.571
    public static final int PI_DIV_4 = 785;   // 0.785
    public static final int E = 2718;         // 2.718
    public static final int SQRT_2 = 1414;    // 1.414
    public static final int DEG_TO_RAD = 17;  // 0.01745... (實際 17.45,此處簡化)
    
    /** 整數轉定點數 */
    public static int toFixed(int i) {
        return i * SCALE;
    }
    
    /** 定點數轉整數 (截斷) */
    public static int toInt(int fixed) {
        return fixed / SCALE;
    }
    
    /** 定點數轉整數 (四捨五入) */
    public static int toIntRounded(int fixed) {
        return (fixed + (fixed >= 0 ? HALF_SCALE : -HALF_SCALE)) / SCALE;
    }
    
    /** 定點數加法 */
    public static int add(int a, int b) {
        return a + b;
    }
    
    /** 定點數減法 */
    public static int sub(int a, int b) {
        return a - b;
    }
    
    /** 定點數乘法 (結果需除以 SCALE) */
    public static int mul(int a, int b) {
        // 防溢位:長整數中間運算
        return (int)(((long)a * b) / SCALE);
    }
    
    /** 定點數除法 (被除數先乘 SCALE) */
    public static int div(int a, int b) {
        if (b == 0) throw new ArithmeticException("Division by zero");
        return (int)(((long)a * SCALE) / b);
    }
    
    /** 定點數平方根 (牛頓迭代法,整數版) */
    public static int sqrt(int a) {
        if (a <= 0) return 0;
        int x = a;
        int y = (x + div(a, x)) >> 1;
        while (y < x) {
            x = y;
            y = (x + div(a, x)) >> 1;
        }
        return x;
    }
    
    /** 定點數正弦 (查表法 + 線性插值,極快) */
    private static final int[] SIN_TABLE = {
        0, 175, 349, 523, 698, 872, 1045, 1219, 1392, 1564, 1736, 1908,
        2079, 2250, 2419, 2588, 2756, 2924, 3090, 3256, 3420, 3584, 3746, 3907,
        4067, 4226, 4384, 4540, 4695, 4848, 5000, 5150, 5299, 5446, 5592, 5736,
        5878, 6018, 6157, 6293, 6428, 6561, 6691, 6820, 6947, 7071, 7193, 7314,
        7431, 7547, 7660, 7771, 7880, 7986, 8090, 8192, 8290, 8387, 8480, 8572,
        8660, 8746, 8829, 8910, 8988, 9063, 9135, 9205, 9272, 9336, 9397, 9455,
        9511, 9563, 9613, 9659, 9703, 9744, 9781, 9816, 9848, 9877, 9903, 9925,
        9945, 9962, 9976, 9986, 9994, 9998, 10000
    };  // 0~90 度,值已 * 10000
    
    public static int sin(int deg) {
        // 正規化至 0~360
        deg = deg % 360;
        if (deg < 0) deg += 360;
        
        int sign = 1;
        if (deg > 180) {
            deg -= 180;
            sign = -1;
        }
        if (deg > 90) {
            deg = 180 - deg;
        }
        
        // 線性插值
        int idx = deg;
        int val = SIN_TABLE[idx];
        if (idx < 90) {
            int nextVal = SIN_TABLE[idx + 1];
            val += (nextVal - val) * (deg % 1);  // 整數度無小數部,此處為 0
        }
        return (val * sign) / 10;  // 轉換為 *1000 縮放
    }
    
    /** 定點數餘弦 */
    public static int cos(int deg) {
        return sin(deg + 90);
    }
}

使用範例:遊戲物理/移動計算

public class GamePhysics {
    // 位置、速度、加速度皆用定點數 (單位:像素 * 1000)
    int x = FixedPointMath.toFixed(100);  // 100.000 像素
    int vx = FixedPointMath.toFixed(50);  // 50.000 px/幀
    int ax = 0;
    
    public void update() {
        // v = v + a
        vx = FixedPointMath.add(vx, ax);
        // x = x + v
        x = FixedPointMath.add(x, vx);
    }
    
    public int getScreenX() {
        return FixedPointMath.toIntRounded(x);  // 繪製時取整數像素
    }
    
    // 發射角度 45 度,速度 100
    public void shoot(int angleDeg, int speed) {
        int rad = FixedPointMath.mul(angleDeg, FixedPointMath.DEG_TO_RAD);
        // 簡化:直接用查表 sin/cos (度數版)
        vx = FixedPointMath.mul(speed, FixedPointMath.cos(angleDeg));
        int vy = FixedPointMath.mul(speed, FixedPointMath.sin(angleDeg));
    }
}

精度與效能權衡指南

| 縮放因子 | 小數位數 | 適用場景 | 優點 | 缺點 | |----------|----------|----------|------|------| | 100 (10^2) | 2 | UI 佈局、簡單幾何 | 直觀、除法快 | 精度有限 | | 1000 (10^3) | 3 | **通用推薦**、遊戲物理 | 精度足夠、十進制友好 | 乘法需長整數防溢位 | | 256 (2^8) | ~2.4 | **極致效能**、粒子系統 | 乘除 = 位移動、極快 | 精度非十進制、難除錯 | | 65536 (2^16) | ~4.8 | 高精度需求 | 精度高、位運算 | 32位整數極易溢位,需 64位中間值 |

避坑指南

  1. 乘法溢位: `a * b` 可能超過 32 位有符號整數範圍 (±21億)。**必須用 `long` 中間運算**:`(int)(((long)a * b) / SCALE)`
  2. 除法精度損失: 先乘 `SCALE` 再除:`((long)a * SCALE) / b`,不可 `a / b * SCALE`
  3. 負數四捨五入: `fixed >= 0 ? +HALF : -HALF`,不可通用 `+HALF`
  4. 累積誤差: 長序列運算誤差會累積,關鍵節點重新從高精度源頭計算
  5. 混用縮放因子: 嚴禁混用不同 `SCALE` 的定點數運算,統一專案級常數

MathFP 庫:更專業的選擇

若專案大量浮點運算,建議引入 **MathFP** (JavaME 定點數學庫):

  • 完整實現 `FP` 類別封裝定點數
  • 支援三角函數、對數、指數、平方根
  • 查表 + 插值 + 牛頓法,精度與效能平衡佳
  • API 類似 `Math`,遷移成本低
// MathFP 使用範例
import org.mathfp.FP;

FP x = FP.fromInt(10);
FP y = FP.fromInt(3);
FP z = FP.div(x, y);  // 3.333...
FP sin = FP.sin(FP.PI_DIV_4);  // sin(45°) ≈ 0.707

總結

CLDC 1.0 無浮點數不是阻礙,而是**倒逼工程師理解數值計算本質**的機會。定點數運算核心:**整數運算 + 固定縮放因子 + 手動小數點管理**。掌握此技術,不僅能在 J2ME/嵌入式/舊平台開發中遊刃有餘,更能深入理解:

  • GPU 定點數著色器 (早期 OpenGL ES 1.x)
  • DSP 數位訊號處理器 (Q 格式定點數)
  • 區塊鏈/智能合約 (Solidity 固定精度數學)
  • 金融系統 (避免浮點誤差的 `BigInteger` 定點實現)

**工程智慧:在約束中尋找通用原理,而非抱怨工具不足。**

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved