xls: 17 title: XFL Developer-friendly representation of XRPL balances descrption: Introduces developer-friendly representation of XRPL balances. author: RichardAH (@RichardAH) created: 2021-03-19 status: Final category: Protocol
Background¶
The XRP ledger allows for two types of balances on accounts:
- Native
xrp
balances - IOU/Token
trustline
balances
Native balances are encoded and processed as signed 63bit integer values with an implicit decimal point at the 6th place from the right. A single unit is referred to as a drop. Thus the smallest possible value is 1 drop
represented logically as: 0.000001 xrp
.
Trustline balances are encoded and processed as a 63 bit decimal floating point number in the following format:
{sign bit
8 bits of exponent
54 bits of mantissa
}
Note: This is not the IEEE floating point format (which is base 2.)
- The exponent is biased by 97. Thus an encoded exponent of
0b00000000
is10^(-97)
. - The mantissa is normalised between
10^15
and10^16 - 1
- The canonical zero of this format is all bits 0.
A 64th disambiguation bit is prepended (most significant bit) to both datatypes, which is 0
in the case of a native balance and 1
in the case of a trustline balance. [1]
Problem Definition¶
Performing computations between these two number formats can be difficult and error-prone for third party developers.
One existing partial solution is passing these amounts around as human-readable decimal numbers encoded as strings. This is computationally intensive and still does not allow numbers of one type to be mathematically operated on with numbers of the other type in a consistent and predictable way.
Internally the XRPL requires two independent types, however any xrp balance less than 10B
will translate without loss of precision into the trustline balance type. For amounts of xrp above 10B
(but less than 100B
) only 1 significant figure is lost from the least significant side. Thus in the worst possible case (moving >= 10B
XRP) 10
drops may be lost from an amount.
The benefits of representing xrp balances in the trustline balance format are:
- Much simpler developer experience (less mentally demanding, lower barrier to entry)
- Can compute exchange rates between trustline balances and xrp
- Can store all balance types in a single data type
- A unified singular set of safe math routines which are much less likely to be used wrongly by developers
XFL Format¶
The XFL format is designed for ease of use and maximum compatibility across a wide variety of processors and platforms.
For maximum ease of passing and returning from (pure) functions all XFL numbers are encoded into an enclosing number
which is always a signed 64 bit integer, as follows:
Note: bit 63 is the most significant bit
- bit 63:
enclosing sign bit
always 0 for a valid XFL - bit 62:
internal sign bit
0 = negative 1 = positive. note: this is not the int64_t's sign bit. - bit 61 - 53:
exponent
biased such that0b000000000
= -97 - bit 53 - 0:
mantissa
between10^15
and10^16 - 1
Special case:
- Canonical zero: enclosing number = 0
Any XFL with a negative enclosing sign bit is invalid
. This DOES NOT refer to the internal sign bit inside the XFL format. It is definitely possible to have a negative value represented in an XFL, however these always exist with a POSITIVE enclosing sign bit.
Invalid (negative enclosing sign bit) XFL values are reserved for propagation of error codes. If an invalid XFL is passed to an XFL processing function (for example float_multiply
) it too should return an invalid XFL.
Examples¶
Number | Enclosing | To String |
---|---|---|
-1 | 1478180677777522688 | -1000000000000000 * 10^(-15) |
0 | 0000000000000000000 | [canonical zero] |
+1 | 6089866696204910592 | +1000000000000000 * 10^(-15) |
+PI | 6092008288858500385 | +3141592653589793 * 10^(-15) |
-PI | 1480322270431112481 | -3141592653589793 * 10^(-15) |
Reference Implementations¶
Javascript:
const minMantissa = 1000000000000000n;
const maxMantissa = 9999999999999999n;
const minExponent = -96;
const maxExponent = 80;
function make_xfl(exponent, mantissa) {
// convert types as needed
if (typeof exponent != "bigint") exponent = BigInt(exponent);
if (typeof mantissa != "bigint") mantissa = BigInt(mantissa);
// canonical zero
if (mantissa == 0n) return 0n;
// normalize
let is_negative = mantissa < 0;
if (is_negative) mantissa *= -1n;
while (mantissa > maxMantissa) {
mantissa /= 10n;
exponent++;
}
while (mantissa < minMantissa) {
mantissa *= 10n;
exponent--;
}
// canonical zero on mantissa underflow
if (mantissa == 0) return 0n;
// under and overflows
if (exponent > maxExponent || exponent < minExponent) return -1; // note this is an "invalid" XFL used to propagate errors
exponent += 97n;
let xfl = !is_negative ? 1n : 0n;
xfl <<= 8n;
xfl |= BigInt(exponent);
xfl <<= 54n;
xfl |= BigInt(mantissa);
return xfl;
}
function get_exponent(xfl) {
if (xfl < 0n) throw "Invalid XFL";
if (xfl == 0n) return 0n;
return ((xfl >> 54n) & 0xffn) - 97n;
}
function get_mantissa(xfl) {
if (xfl < 0n) throw "Invalid XFL";
if (xfl == 0n) return 0n;
return xfl - ((xfl >> 54n) << 54n);
}
function is_negative(xfl) {
if (xfl < 0n) throw "Invalid XFL";
if (xfl == 0n) return false;
return ((xfl >> 62n) & 1n) == 0n;
}
function to_string(xfl) {
if (xfl < 0n) throw "Invalid XFL";
if (xfl == 0n) return "<zero>";
return (
(is_negative(xfl) ? "-" : "+") +
get_mantissa(xfl) +
" * 10^(" +
get_exponent(xfl) +
")"
);
}
C:
- See implementation in Hooks: here