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

Java 與 C# protobuf-net 互通:bcl.Decimal、bcl.DateTime 精確轉換實作指南

     編輯:Java 綜合教學

跨語言 Protobuf 序列化挑戰

企業級系統常面臨 C# 服務使用 protobuf-net(含 bcl.proto 標準類型)與 Java 服務使用官方 protobuf-java 互通的場景。核心難點在於 bcl.Decimal 與 bcl.DateTime 這兩個 .NET 特有型別的精確映射。

bcl.Decimal 內部結構解析

bcl.Decimal 以 128 位元表示十進制數值:

  • hi (int32):高 32 位元
  • lo (int64):低 64 位元
  • signScale (int32):bit 0 為符號(0=正,1=負),bit 1-16 為小數位數

組合形成 96 位元無符號整數 + 符號 + 縮放因子,對應 Java BigDecimal。

核心轉換邏輯

bcl.Decimal → BigDecimal

public static BigDecimal bclDecimalToBigDecimal(Bcl.Decimal bclDecimal) {
    if (bclDecimal == null) return BigDecimal.ZERO;
    
    // 1-2. 高低位轉大端位元組陣列
    byte[] deBytes = new byte[12];
    int2byte(deBytes, 0, bclDecimal.getHi());   // hi → 位元組 0-3
    long2byte(deBytes, 4, bclDecimal.getLo());  // lo → 位元組 4-11
    
    // 3. 解析 signScale
    int signScale = bclDecimal.getSignScale();
    int sign = (signScale & 1) == 1 ? -1 : 1;           // bit 0
    int scale = (signScale & 0xFFFE) >> 1;              // bit 1-16
    
    // 4-5. 建構 BigInteger → BigDecimal
    BigInteger unscaled = new BigInteger(sign, deBytes);
    return new BigDecimal(unscaled, scale);
}

BigDecimal → bcl.Decimal

public static Bcl.Decimal bigDecimalToBclDecimal(BigDecimal bigDecimal) {
    if (bigDecimal == null) return null;
    
    Bcl.Decimal.Builder builder = Bcl.Decimal.newBuilder();
    BigInteger unscaled = bigDecimal.unscaledValue(); // 必須用未縮放值
    int scale = bigDecimal.scale();
    
    // 1. 整數轉 12 位元組大端陣列(左側補零)
    byte[] deBytes = new byte[12];
    byte[] src = unscaled.toByteArray();
    System.arraycopy(src, 0, deBytes, 12 - src.length, src.length);
    
    // 2. 切分 hi/lo
    builder.setHi(bytes2int(deBytes, 0));
    builder.setLo(bytes2long(deBytes, 4));
    
    // 3. 編碼 signScale
    int signScale = (unscaled.signum() < 0 ? 1 : 0) | (scale << 1);
    builder.setSignScale(signScale);
    
    return builder.build();
}

完整工具類 BclUtil

public final class BclUtil {
    private static final long TICKS_PER_MILLISECOND = 10000L;
    private static final int SIGN_SCALE_MASK = 0xFFFE; // bit 1-16
    
    private BclUtil() {}
    
    // --- 位元組轉換輔助(大端序)---
    public static void int2byte(byte[] dst, int pos, int val) {
        dst[pos]     = (byte) (val >>> 24);
        dst[pos + 1] = (byte) (val >>> 16);
        dst[pos + 2] = (byte) (val >>> 8);
        dst[pos + 3] = (byte) val;
    }
    
    public static void long2byte(byte[] dst, int pos, long val) {
        int2byte(dst, pos,     (int) (val >>> 32));
        int2byte(dst, pos + 4, (int) val);
    }
    
    public static int bytes2int(byte[] src, int pos) {
        return ((src[pos] & 0xFF) << 24) |
               ((src[pos+1] & 0xFF) << 16) |
               ((src[pos+2] & 0xFF) << 8) |
               (src[pos+3] & 0xFF);
    }
    
    public static long bytes2long(byte[] src, int pos) {
        return (((long) bytes2int(src, pos)) << 32) |
               (bytes2int(src, pos + 4) & 0xFFFFFFFFL);
    }
    
    // --- 核心轉換 API ---
    public static BigDecimal toBigDecimal(Bcl.Decimal src) { ... }
    public static Bcl.Decimal toBclDecimal(BigDecimal src) { ... }
    public static Date toDate(Bcl.DateTime src) { ... }
    public static Bcl.DateTime toBclDateTime(Date src) { ... }
    
    // --- DateTime 轉換(Ticks ↔ Millis)---
    // .NET Ticks: 100ns 間隔從 0001-01-01 起算
    // Java Date: 毫秒從 1970-01-01 起算
    private static final long EPOCH_DIFF_TICKS = 621355968000000000L;
    
    public static Date toDate(Bcl.DateTime dt) {
        long ticks = (dt.getHigh() << 32) | (dt.getLow() & 0xFFFFFFFFL);
        long millis = (ticks - EPOCH_DIFF_TICKS) / TICKS_PER_MILLISECOND;
        return new Date(millis);
    }
    
    public static Bcl.DateTime toBclDateTime(Date date) {
        long ticks = date.getTime() * TICKS_PER_MILLISECOND + EPOCH_DIFF_TICKS;
        return Bcl.DateTime.newBuilder()
            .setHigh((int) (ticks >>> 32))
            .setLow((int) ticks)
            .build();
    }
}

關鍵測試驗證點

測試案例輸入預期輸出
正數帶小數123.456scale=3, unscaled=123456
負數-0.001sign=-1, scale=3
大數值12345678901234567890.1234596位整數無溢位
DateTime 邊界0001-01-01 / 9999-12-31Ticks 正確映射

避坑指南

  1. 必須使用 unscaledValue() 而非 toBigInteger(),後者會套用 scale
  2. 位元組序固定為大端,跨平台一致性關鍵
  3. signScale 位運算:sign = (v & 1)、scale = (v >>> 1)
  4. DateTime 紀元差:.NET 從 1 年起算,Java 從 1970 年,差值為常數
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved