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:
| field | bits | description |
|---|---|---|
flags | [31] sign, [23:16] scale | sign bit and decimal exponent |
hi | [95:64] of mantissa | high 32 bits (zero when abs value fits in u64) |
lo | [31:0] of mantissa | low 32 bits of the 96-bit mantissa |
mid | [63:32] of mantissa | middle 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
| input | scale | decimal value | flags | hi | lo | mid |
|---|---|---|---|---|---|---|
100000000 | 8 | 1.00000000 | 0x00080000 | 0x00000000 | 0x05F5E100 | 0x00000000 |
-50000000 | 8 | -0.50000000 | 0x80080000 | 0x00000000 | 0x02FAF080 | 0x00000000 |
123456789012 | 8 | 1234.56789012 | 0x00080000 | 0x00000000 | 0xBE991A14 | 0x0000001C |