Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Decimal Encoding

The Bullet exchange uses rust_decimal::Decimal (referred to as RustDecimal) for prices and sizes. This page explains how to encode a fixed-point integer (i64 value + u8 scale) into the 128-bit decimal layout used for borsh serialization.

Layout

A RustDecimal has four u32 fields:

fieldbitsdescription
flags[31] sign, [23:16] scalesign bit and decimal exponent
hi[95:64] of mantissahigh 32 bits (zero when abs value fits in u64)
lo[31:0] of mantissalow 32 bits of the 96-bit mantissa
mid[63:32] of mantissamiddle 32 bits

The mantissa is an unsigned 96-bit integer. The represented value is:

(-1)^sign * mantissa / 10^scale

For borsh serialization the fields are ordered as: flags, hi, lo, mid.

Converting from fixed-point to RustDecimal

If your system represents values as i64 with a known scale (e.g. N * 10^-scale), the conversion is straightforward — the absolute value of the i64 is the mantissa, and the scale goes into the flags field.

C++

// borsh wire layout: flags, hi, lo, mid
struct RustDecimal {
    uint32_t flags;
    uint32_t hi;
    uint32_t lo;
    uint32_t mid;
};

/// convert a fixed-point i64 with the given scale to RustDecimal
/// e.g. from_fixed(123456789, 8) encodes 1.23456789
RustDecimal from_fixed(int64_t value, uint8_t scale) {
    bool negative = value < 0;
    uint64_t abs_value = negative ? -static_cast<uint64_t>(value)
                                  : static_cast<uint64_t>(value);

    return RustDecimal{
        .flags = (static_cast<uint32_t>(scale) << 16)
               | (negative ? 0x80000000u : 0u),
        .hi  = 0,
        .lo  = static_cast<uint32_t>(abs_value),
        .mid = static_cast<uint32_t>(abs_value >> 32),
    };
}

Python

# borsh wire layout: flags, hi, lo, mid
@dataclass
class RustDecimal:
    flags: int
    hi: int
    lo: int
    mid: int

def from_fixed(value: int, scale: int) -> RustDecimal:
    """convert a fixed-point int with the given scale to RustDecimal
    e.g. from_fixed(123456789, 8) encodes 1.23456789"""
    negative = value < 0
    abs_value = abs(value)

    flags = (scale << 16) | (0x80000000 if negative else 0)
    lo = abs_value & 0xFFFFFFFF
    mid = (abs_value >> 32) & 0xFFFFFFFF
    hi = 0

    return RustDecimal(flags, hi, lo, mid)

JavaScript

// convert a fixed-point bigint with the given scale to RustDecimal fields
// borsh wire layout: flags, hi, lo, mid
// e.g. fromFixed(123456789n, 8) encodes 1.23456789
function fromFixed(value, scale) {
    const negative = value < 0n;
    const absValue = negative ? -value : value;

    const flags = (scale << 16) | (negative ? 0x80000000 : 0);
    const lo = Number(absValue & 0xFFFFFFFFn);
    const mid = Number((absValue >> 32n) & 0xFFFFFFFFn);
    const hi = 0;

    return { flags: flags >>> 0, hi, lo, mid };
}

Rust

The rust_decimal crate handles this natively — parse from string or construct via Decimal::from_parts(lo, mid, hi, negative, scale).

Examples

inputscaledecimal valueflagshilomid
10000000081.000000000x000800000x000000000x05F5E1000x00000000
-500000008-0.500000000x800800000x000000000x02FAF0800x00000000
12345678901281234.567890120x000800000x000000000xBE991A140x0000001C