currency_rs is library for working with currency values. it is inspired by currency.js which was built to work around floating point issues in javascript.
currency_rs works with values as integers behind the scenes, resolving some of the most basic precision problems.
2.51 + 0.01; // 2.5199999999999996
Currency::new_float(2.51, None).add(0.01).value(); // 2.52
2.52 - 0.01; // 2.5100000000000002
Currency::new_float(2.52, None).subtract(0.01).value(); // 2.51
This should work for most reasonable values of currencies. As long as your currency values are less than 253 (in cents) or 90,071,992,547,409.91 you should be okay.
[dependencies]
currency_rs = "x.y.z"
You can create a currency object from float, strings, or the currency object itself as values.
Currency::new_float(123., None).to_string(); // 123.00
Currency::new_float(1.23, None).to_string(); // 1.23
Currency::new_string("1.23", None).unwrap().to_string(); // 1.23
Currency::new_string("$12.30", None).unwrap().to_string(); // 12.30
let value = Currency::new_string("123.45", None).unwrap();
Currency::new_cur(value, None).to_string(); // 123.45
Currency accepts decimal values (i.e. 1.23
) with a default precision of 2, but can accept a minor currency unit (e.g. cents in a dollar). This will respect the precision option when parsing.
let opt = CurrencyOpts::new().set_from_cents(true);
Currency::new_float(123., Some(opt.clone())).to_string(); // 1.23
Currency::new_string("123", Some(opt))
.unwrap()
.to_string(); // 1.23
let opt = CurrencyOpts::new()
.set_from_cents(true)
.set_precision(0);
Currency::new_float(123., Some(opt)).to_string(); // 123
let opt = CurrencyOpts::new()
.set_from_cents(true)
.set_precision(3);
Currency::new_float(123., Some(opt)).to_string(); // 1.23
There's various arithmetic methods that help take the guesswork out of trying to resolve floating point problems.
Currency::new_float(123.5, None).add(0.23).value(); // 123.73
Currency::new_float(5.0, None).subtract(0.5).value(); // 4.50
Currency::new_float(45.25, None).multiply(3.).value(); // 135.75
Currency::new_float(1.12, None)
.distribute(5)
.iter()
.map(|x| x.value())
.collect::<Vec<f64>>(); // [0.23, 0.23, 0.22, 0.22, 0.22]
It also supports Multiplication
, Subtraction
, Division
, Addition
and Assignment
Operators
(Currency::new_float(123.5, None) + 0.23).value(); // 123.73
(Currency::new_float(5.0, None) - 0.5).value(); // 4.50
(Currency::new_float(45.25, None) * 3.).value(); // 135.75
Currency::new_float(1.12, None)
.distribute(5)
.iter()
.map(|x| x.value())
.collect::<Vec<f64>>(); // [0.23, 0.23, 0.22, 0.22, 0.22]
There's even a built in formatter that will automatically place comma delimiters in the right place.
Currency::new_string("2,573,693.75", None)
.unwrap()
.add_string("100,275.50")
.unwrap()
.format(); // "$2,673,969.25"
Currency::new_string("1,237.72", None)
.unwrap()
.subtract(300.)
.format(); // "$937.72"
You can also change the format, localizing the decimal and/or delimiter to your locale.
fn euro(value: &str) -> Currency {
let otp = CurrencyOpts::new()
.set_symbol("€")
.set_separator(".")
.set_decimal(",");
Currency::new_string(value, Some(otp)).unwrap()
}
euro("2.573.693,75")
.add_string("100.275,50")
.unwrap()
.format(); // "€2.673.969,25"
euro("1.237,72")
.subtract(300.)
.format(); // "€937,72"
currency_rs comes with its own set of default options conforming to USD. You can customize these according to your locale.
symbol
default: $
Currency symbol included when calling currency.format()
.
separator
default: ,
Separator dividing the number groups when calling currency.format()
.
decimal
default: .
Decimal used when calling currency.format()
.
precision
default: 2
Number of decimal places to store as cents.
pattern
default: !#
Allows you to customize the format pattern using !
as replacement for the currency symbol and #
as replacement for the currency amount.
negative_pattern
default: -!#
Allows you to customize the negative format pattern using !
as replacement for the currency symbol and #
as replacement for the currency amount.
error_on_invalid
default: false
If an invalid value such as abc
is passed in to Currency::new_string
, will throw an error.
increment
default: null
When implementing a currency that implements rounding, setting the increment value will allow you to set the closest increment to round the display value to.
let otp = CurrencyOpts::new()
.set_increment(0.05);
Currency::new_float(1.48, Some(otp)).format(); // "$1.50"
use_vedic
default: false
Formats number groupings using the Indian Numbering System, i.e. 10,00,000.00
from_cents
default: false
Parse the amount value as a minor currency unit (e.g. cents in a dollar) instead of dollars.
let otp = CurrencyOpts::new()
.set_separator(" ")
.set_decimal(",")
.set_symbol("€");
Currency::new_float(1234.45, Some(otp)).format(); // "€1 234,45"
If you need to work with multiple currency values, the easiest way is to setup factory functions with your required currency settings.
fn usd(value: f64) -> Currency {
let otp = CurrencyOpts::new()
.set_symbol("$")
.set_precision(2);
Currency::new_float(value, Some(otp))
}
fn jpy(value: f64) -> Currency {
let otp = CurrencyOpts::new()
.set_symbol("¥")
.set_precision(0);
Currency::new_float(value, Some(otp))
}
fn gas(value: f64) -> Currency {
let otp = CurrencyOpts::new()
.set_precision(3);
Currency::new_float(value, Some(otp))
}
usd(1234.56).format(); // "$1,234.56"
jpy(1234.56).format(); // "¥1,235"
gas(1234.56).format(); // "$1,234.560"