You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Postgres中NUMERIC类型如何序列化?Rust中u128转numeric(40)遇格式难题

解决Rust中u128序列化为Postgres numeric(40)的问题

Postgres的numeric类型有特定的二进制格式,不是简单的大小端整数序列化,需要按照其结构组装字节流。以下是具体实现步骤和代码:

Postgres numeric二进制格式核心结构(大端字节序)

所有多字节字段均采用大端(网络字节序),结构如下:

  • 4字节uint32:整个numeric的总字节长度(包含此字段)
  • 2字节int16:十进制数字的总位数(如1234为4位)
  • 2字节int16:权重(weight),最高有效digit对应的10^(3*weight),计算方式为(总位数-1)/3(整数除法)
  • 2字节int16:符号位(0=正,1=负)
  • 2字节int16:精度(如numeric(40)的精度为40)
  • 2字节int16:小数位(scale,numeric(40,0)为0)
  • 后续字节:每个digit为2字节uint16,对应3个十进制数字(从最高位到最低位存储,不足3位的组直接存实际数值)

Rust实现ToSql trait

针对u128实现Postgres的ToSql trait,完成序列化:

use postgres::{types::{ToSql, Type}, Error};
use std::io::Write;

impl ToSql for u128 {
    fn to_sql(&self, ty: &Type, out: &mut dyn Write) -> Result<usize, Error> {
        if *ty != Type::NUMERIC {
            return Err(Error::ToSqlConversionFailed);
        }

        // 将u128转为十进制字符串
        let s = self.to_string();
        let total_digits = s.len() as i16;
        
        // 从右往左拆分每3位一组,再反转得到高位到低位的顺序
        let mut chars = s.chars().rev().collect::<Vec<_>>();
        let mut digits = Vec::new();
        while !chars.is_empty() {
            let chunk = chars.drain(0..3.min(chars.len())).rev().collect::<String>();
            digits.push(chunk.parse::<u16>().unwrap());
        }
        digits.reverse();

        // 计算numeric字段值
        let weight = (total_digits - 1) / 3;
        let sign = 0; // u128为无符号,固定正
        let precision = 40; // 对应numeric(40)的精度
        let scale = 0; // numeric(40,0)的小数位
        let total_len = 4 + 2 * 5 + 2 * digits.len() as u32;

        // 按格式写入字节流(大端)
        out.write_all(&total_len.to_be_bytes())?;
        out.write_all(&total_digits.to_be_bytes())?;
        out.write_all(&weight.to_be_bytes())?;
        out.write_all(&sign.to_be_bytes())?;
        out.write_all(&precision.to_be_bytes())?;
        out.write_all(&scale.to_be_bytes())?;
        for &digit in &digits {
            out.write_all(&digit.to_be_bytes())?;
        }

        Ok(total_len as usize)
    }

    fn accepts(ty: &Type) -> bool {
        *ty == Type::NUMERIC
    }

    to_sql_checked!();
}

关键注意事项

  1. 字节序:所有多字节字段必须用大端写入,Postgres不支持小端格式。
  2. digit顺序:拆分后必须反转数组,确保高位digit在前,符合Postgres的存储要求。
  3. 精度适配:如果需要适配不同精度的numeric类型,可以通过解析Type的参数动态调整precision和scale,上述代码针对numeric(40,0)做了固定处理。
  4. 测试验证:插入u128::MAX后,在Postgres中查询验证数值是否正确:
    SELECT col FROM your_table;
    -- 应返回340282366920938463463374607431768211455
    

内容的提问来源于stack exchange,提问作者João A. Veiga

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 17:39:54