fractal_utils/amount.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
//! Fractal Global Credits amount
//!
//! This module holds the `Amount` type and the `AmountParseError`. It will eventually hold `MAX`
//! and `MIN` values for `Amount`s when constant expressions are implemented in the compiler in the
//! stable chanel.
//!
//! The maximum and minimum amount values can in any case be known by using `max_value()` and
//! `min_value()` functions in the `Amount` type:
//!
//! ```
//! use std::u64;
//! use fractal_utils::Amount;
//!
//! let max_value = Amount::max_value();
//! let min_value = Amount::min_value();
//!
//! assert_eq!(max_value, Amount::from_repr(u64::MAX));
//! assert_eq!(min_value, Amount::from_repr(u64::MIN));
//! ```
#![allow(trivial_numeric_casts)]
use std::convert::From;
use std::{fmt, str, u64};
use std::str::FromStr;
use std::result::Result;
use std::error::Error;
use std::ops::{Add, AddAssign, Sub, SubAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign};
use std::num::ParseIntError;
use rustc_serialize::{Encodable, Decodable, Encoder, Decoder};
#[cfg(feature = "json-types")]
use rustc_serialize::json;
use super::CURRENCY_SYMBOL;
// Largest amount value
// pub const MAX: Amount = Amount::max_value();
// Smallest amount value
// pub const MIN: Amount = Amount::min_value();
/// Fractal Global Credits amount
///
/// This struct can be used the same way as any other number. An `Amount` can be added or
/// substracted to another `Amount`, and it can be divided and multiplied by an integer. All
/// operations that are defined in the `Amount` scope and that are exact can be used directly as
/// usual integer / float point operations.
///
/// No negative amounts can exist, since an `Amount` is unsigned, sothe negation operator '-',
/// then, has no use with an `Amount`.
///
/// Its internal representation is a 64 bit unsigned number, that is displayed as a fixed point,
/// number of factor 1/1,000. This means that an internal representation of `1,000` would be an
/// external amount of `1`. The internal representation shouldn't be used except when serializing
/// and deserializing the data, since this type is sent in *JSON* as its internal `u64`.
///
/// The use is the following:
///
/// ```
/// use fractal_utils::Amount;
///
/// let amount = Amount::from_repr(1_654); // 1.654
/// let ten = Amount::from_repr(10_000); // 10
/// let add_ten = amount + ten;
/// assert_eq!(add_ten, Amount::from_repr(11_654)); // 11.654
/// ```
///
/// They can be divided and multiplied by any other unsigned integer:
///
/// ```
/// # use fractal_utils::Amount;
/// #
/// let mut amount = Amount::from_repr(7_000); // 7
/// amount *= 10u32;
/// assert_eq!(amount, Amount::from_repr(70_000)); // 70
///
/// amount = amount / 30u16;
/// assert_eq!(amount, Amount::from_repr(2_333)); // 2.333
///
/// amount %= 1u8;
/// assert_eq!(amount, Amount::from_repr(333)); // 0.333
/// ```
///
/// Amounts can easily be displayed using the `Display` trait as any other number:
///
/// ```
/// # use fractal_utils::Amount;
/// #
/// let amount = Amount::from_repr(56_000);
/// assert_eq!(format!("{}", amount), "56");
/// assert_eq!(format!("{:.2}", amount), "56.00");
/// assert_eq!(format!("{:.5}", amount), "56.00000");
/// assert_eq!(format!("{:05.1}", amount), "056.0");
///
/// // And with rounding:
/// let amount = Amount::from_repr(56); // 0.056
/// assert_eq!(format!("{:.2}", amount), "0.06");
/// ```
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Amount {
value: u64,
}
impl Amount {
/// Creates a new amount from its internal representation.
pub fn from_repr(value: u64) -> Amount {
Amount { value: value }
}
/// Gets the internal representation of the amount.
pub fn get_repr(&self) -> u64 {
self.value
}
/// Returns the smallest value that can be represented as a currency amount.
pub fn min_value() -> Amount {
Amount { value: u64::MIN }
}
/// Returns the largest value that can be represented as a currency amount.
pub fn max_value() -> Amount {
Amount { value: u64::MAX }
}
}
#[cfg(feature = "json-types")]
/// The Amount type can easily be converted to json, using its `to_json()` method. Note that this
/// will print the amount as a float, with up to three decimals. In high amounts this can lead to
/// unnacuracies when printed. This can be avoided by using the `Display` trait.
impl json::ToJson for Amount {
fn to_json(&self) -> json::Json {
json::Json::F64(self.value as f64 / 1000.0)
}
}
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let units = self.value / 1_000;
let decimal_repr = self.value % 1_000;
let result = match f.precision() {
None => {
if decimal_repr == 0 {
format!("{}", units)
} else if decimal_repr % 100 == 0 {
format!("{}.{:01}", units, decimal_repr / 100)
} else if decimal_repr % 10 == 0 {
format!("{}.{:02}", units, decimal_repr / 10)
} else {
format!("{}.{:03}", units, decimal_repr)
}
}
Some(0) => {
format!("{}",
if decimal_repr >= 500 {
units + 1
} else {
units
})
}
Some(1) => {
format!("{}.{:01}",
units,
if decimal_repr % 100 >= 50 {
decimal_repr / 100 + 1
} else {
decimal_repr / 100
})
}
Some(2) => {
format!("{}.{:02}",
units,
if decimal_repr % 10 >= 5 {
decimal_repr / 10 + 1
} else {
decimal_repr / 10
})
}
Some(p) => {
let mut string = format!("{}.{:03}", units, decimal_repr);
for _ in 3..p {
string.push('0');
}
string
}
};
match f.width() {
None => write!(f, "{}", result),
Some(w) => {
if w < result.len() {
write!(f, "{}", result)
} else {
let mut pad = String::new();
for _ in result.len()..w {
pad.push('0');
}
write!(f, "{}{}", pad, result)
}
}
}
}
}
/// Amount parsing error.
///
/// This struct represents an amount parsing error. It explains the exact error that lead to the
/// parsing error, and implements common `Error` and `Display` traits.
#[derive(Debug)]
pub struct AmountParseError {
description: String,
cause: Option<ParseIntError>,
}
impl AmountParseError {
fn new<S: AsRef<str>>(amount: S, error: S, cause: Option<ParseIntError>) -> AmountParseError {
AmountParseError {
description: format!("the amount {:?} is not a valid Fractal Global amount, {}",
amount.as_ref(),
error.as_ref()),
cause: cause,
}
}
}
impl fmt::Display for AmountParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description)
}
}
impl Error for AmountParseError {
fn description(&self) -> &str {
&self.description
}
fn cause(&self) -> Option<&Error> {
match self.cause.as_ref() {
Some(c) => Some(c),
None => None,
}
}
}
impl FromStr for Amount {
type Err = AmountParseError;
fn from_str(s: &str) -> Result<Amount, AmountParseError> {
if s.contains('.') {
let parts = s.split('.').count();
let mut split = s.split('.');
match parts {
2 => {
let units_str = split.next().unwrap();
let units: u64 = if units_str != "" {
match units_str.parse::<u64>() {
Ok(u) => {
if u <= u64::MAX / 1_000 {
u * 1_000
} else {
return Err(AmountParseError::new(s,
&format!("it is too big, the maximum amount is {}",
Amount::max_value()), None));
}
}
Err(e) => {
return Err(AmountParseError::new(s,
"the units part it is not a \
valid u64 amount",
Some(e)))
}
}
} else {
0
};
let mut decimals_str = String::from(split.next().unwrap());
if decimals_str.is_empty() {
return Err(AmountParseError::new(s,
"no decimals were found after the \
decimal separator",
None));
}
while decimals_str.len() < 3 {
decimals_str.push('0');
}
let decimals: u64 = match decimals_str.parse() {
Ok(d) => {
if decimals_str.len() == 3 {
d
} else {
let divisor = 10u64.pow(decimals_str.len() as u32 - 3);
let rem = d % divisor;
if rem >= divisor / 2 {
d / divisor + 1
} else {
d / divisor
}
}
}
Err(_) => {
return Err(AmountParseError::new(s,
"the decimal part is not a valid \
u64 number",
None))
}
};
if (u64::MAX - decimals) >= units {
Ok(Amount::from_repr(units + decimals))
} else {
Err(AmountParseError::new(s,
&format!("it is too big, the maximum amount \
is {}",
Amount::max_value()),
None))
}
}
_ => {
Err(AmountParseError::new(s,
"an amount can only have one period to separate \
units and decimals",
None))
}
}
} else {
match s.parse::<u64>() {
Ok(v) => {
if v <= u64::MAX / 1_000 {
Ok(Amount::from_repr(v * 1_000))
} else {
Err(AmountParseError::new(s,
&format!("it is too big, the maximum amount \
is {}",
Amount::max_value()),
None))
}
}
Err(_) => Err(AmountParseError::new(s, "it is not a valid u64 number", None)),
}
}
}
}
impl fmt::Debug for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"Amount {{ {:?} }} ({} {})",
self.value,
CURRENCY_SYMBOL,
self)
}
}
impl Encodable for Amount {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
s.emit_u64(self.value)
}
}
impl Decodable for Amount {
fn decode<D: Decoder>(d: &mut D) -> Result<Amount, D::Error> {
match d.read_u64() {
Ok(repr) => Ok(Amount::from_repr(repr)),
Err(e) => Err(e),
}
}
}
macro_rules! impl_ops_int {
($($t:ty)*) => ($(
impl Div<$t> for Amount {
type Output = Amount;
fn div(self, rhs: $t) -> Amount {
Amount { value: self.value / rhs as u64 }
}
}
impl DivAssign<$t> for Amount {
fn div_assign(&mut self, rhs: $t) {
self.value /= rhs as u64
}
}
impl Rem<$t> for Amount {
type Output = Amount;
fn rem(self, rhs: $t) -> Amount {
Amount { value: self.value % (rhs as u64 * 1_000)}
}
}
impl RemAssign<$t> for Amount {
fn rem_assign(&mut self, rhs: $t) {
self.value %= rhs as u64 * 1_000
}
}
impl Mul<$t> for Amount {
type Output = Amount;
fn mul(self, rhs: $t) -> Amount {
Amount { value: self.value * rhs as u64 }
}
}
impl Mul<Amount> for $t {
type Output = Amount;
fn mul(self, rhs: Amount) -> Amount {
Amount { value: self as u64 * rhs.value }
}
}
impl MulAssign<$t> for Amount {
fn mul_assign(&mut self, rhs: $t) {
self.value *= rhs as u64
}
}
)*)
}
impl_ops_int! { u8 u16 u32 u64 usize }
impl Add for Amount {
type Output = Amount;
fn add(self, rhs: Amount) -> Amount {
Amount { value: self.value + rhs.value }
}
}
impl AddAssign for Amount {
fn add_assign(&mut self, rhs: Amount) {
self.value += rhs.value
}
}
impl Sub for Amount {
type Output = Amount;
fn sub(self, rhs: Amount) -> Amount {
Amount { value: self.value - rhs.value }
}
}
impl SubAssign for Amount {
fn sub_assign(&mut self, rhs: Amount) {
self.value -= rhs.value
}
}