#currency #money #xand #tpfs #math

xand_money

Utility crate to safely parse and convert Xand and fiat monetary values

1 stable release

2.0.4 Feb 14, 2023

#1819 in Parser implementations

MIT license

30KB
507 lines

Table of Contents

About

The xand_money submodule provides methods for safely working with monetary values in xand_banks and other crates.

It exposes fiat types such as Usd for currency and a Xand type to represent digital claims. The class is extensible to other fiat currencies.

The Xand type represents values on the Xand network, which cannot be negative.

The fiat type Usd represents monetary values as communicated to/from banks, which can be negative.

This crate is designed to prevent all numeric data loss (rounding and precision errors inherent working with IEE-754-encoded floating point values), which is especially important when working with money.

Getting started

This crate is available from the TPFS internal crates registry.

Add xand_money to your project in Cargo.toml with:

xand_money = { version = "<desired version>", registry = "tpfs" }

Money Types

Usd

Usd's inner type is (currently) a Decimal which can be returned in major units (1.23) or minor units (123). Note: Usd inner type is a private implementation detail and should not be depended upon.

Conversions

Into Usd

The Usd type enables conversions from f64, i64, u64, and &str types we might receive from a third party bank API into a USD monetary representation within xand-banks and other crates.

For example, converting from a float:

let foo: f64 = 1.23;
let usd = Usd::from_f64(foo)?;

From Usd

The Usd type enables conversions to f64:

let usd = Usd::from_f64(15_149.99_f64)?;
let num: f64 = f64::try_from(usd)?;

The Usd type also enables minor unit representations for u64 and i64, though given the differing bounds of these types it is possible for representation to fail, in which case an Error will be returned instead:

let usd = Usd::from_f64(15_149.99_f64)?;
let i_minor_units: i64 = usd.into_i64_minor_units()?;
let u_minor_units: u64 = usd.into_u64_minor_units()?;

Usd into Decimal

Usd can be converted into a Decimal using one of its instance methods, allowing for conversions supported by that type, see docs.

Equality Comparisons

Types in xand_money implement full and partial equality for like types.

To compare different monetary values, convert them into the same money type:

let usd1 = Usd::from_f64(123.00)?;
let usd2 = Usd::from_i64(123);
assert_eq!(usd1, usd2);

Arithmetic Operations

In order to make arithmetic calculations using different monetary value representations (e.g. a Usd and a f64), they must first be converted to Decimal format by using the type's as_major_units() or as_minor_units() instance methods.

For example:

let usd1 = Usd::from_f64(1.23)?;
let usd2 = Usd::from_f64(1.23)?;
let usd3 = Usd::from_i64(1);
assert_ne!(usd1.as_major_units(), usd2.as_major_units() + usd3.as_major_units());

Note: Xand parent class can only be added to itself and cannot be transformed into other types.

Error handling

Errors produced by the xand_money class can be consumed by a more specific error enum in the bank implementation:

foo
.bar()
.map_or_else(
        |e: MoneyError| Err(BazBankAdapterError {
            source: Arc::new(MoneyError::ErrorConstructingDecimal),
            message: "Failed to parse transaction amount from string".to_string()
            }),
        |val| Ok(val))

However, in infallible operations (such as constructing request bodies), the error will not be accessed because a failure case would be a panic:

...
let amt: Usd  = xfer.amount;

XferReq {
    xfer_req: Some(XferReqXferReq {
        ...
        xfer_info: Some(XferInfo {
            ...
            cur_amt: Some(CurAmt {
                amt: match amt.as_major_units().to_f32(){
                    Some(amt) => Some(amt),
                    None => panic!(), // XferReq does not allow this conversion to fail
                }
            })
        }),
    }),

Dependencies

~0.9–1.5MB
~33K SLoC