diff --git a/.gitignore b/.gitignore index 94408df6..a6112e64 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # Generated by Cargo /target/ Cargo.lock +*.swp +*.swo +/decimal-macros/target/ diff --git a/Cargo.toml b/Cargo.toml index 339a503a..56a3a929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,27 @@ [package] name = "decimal" -version = "2.0.4" -authors = ["Alkis Evlogimenos "] +version = "2.4.0" +authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" -repository = "https://github.com/alkis/decimal" -documentation = "https://alkis.github.io/decimal" -keywords = ["decimal", "decNumber"] -license = "Apache-2.0" +autobins = false +edition = "2018" -[badges] -travis-ci = { repository = "alkis/decimal" } +[lib] +name = "decimal" +path = "src/lib.rs" + +[[bin]] +path = "src/bytes.rs" +name = "bytes" + +[[bin]] +path = "src/to_string.rs" +name = "to-string" + +[[bin]] +path = "src/run_test.rs" +name = "run-test" [dependencies] bitflags = "1" @@ -18,12 +29,21 @@ libc = "0.2" ord_subset = { optional = true, version = "3" } rustc-serialize = { optional = true, version = "0.3" } serde = { optional = true, version = "1" } +clap = { features = [], version = "2" } +faster = { git = "https://github.com/AdamNiederer/faster", branch = "master", optional = true } +slog = { version = "2", optional = true } +approx = "0.1" [features] -default = ["ord_subset", "rustc-serialize", "serde"] +default = ["ord_subset", "serde", "slog"] [build-dependencies] cc = "1" [dev-dependencies] serde_json = "1" +rand = "0.4" +decimal-macros = { version = "0.2", path = "decimal-macros" } + +[profile.test] +opt-level = 2 diff --git a/README.md b/README.md index 109fc436..78402040 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ # decimal -[![Travis](https://img.shields.io/travis/alkis/decimal.svg)](https://travis-ci.org/alkis/decimal) -![Downloads](https://img.shields.io/crates/d/decimal.svg) -[![Crates.io](https://img.shields.io/crates/v/decimal.svg)](https://crates.io/crates/decimal) -![Apache license](https://img.shields.io/crates/l/decimal.svg) - Decimal Floating Point arithmetic for rust based on the [decNumber library](http://speleotrove.com/decimal/decnumber.html). diff --git a/build.rs b/build.rs index a3caf569..b0cfbbf0 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,7 @@ fn main() { "0" }; cc::Build::new() + .warnings(false) .include("decNumber") .file("decNumber/decContext.c") .file("decNumber/decDouble.c") diff --git a/decNumber/decCommon.c b/decNumber/decCommon.c index 0825781b..ca598c41 100644 --- a/decNumber/decCommon.c +++ b/decNumber/decCommon.c @@ -1607,6 +1607,16 @@ char * decFloatToString(const decFloat *df, char *string){ pre=(Int)(c-cstart)+exp; // length+exp [c->LSD+1] // [here, pre-exp is the digits count (==1 for zero)] + // if (exp>0) { + // + // if (exp>0 || pre<-7) { + // + // neither of the above variations worked during an attempt to + // remove exponential form entirely from this function. In both + // cases it ended up returning "0.00" for 1e-8 + // + // - JS 6/1/18 + // if (exp>0 || pre<-5) { // need exponential form e=pre-1; // calculate E value pre=1; // assume one digit before '.' diff --git a/decimal-macros/Cargo.toml b/decimal-macros/Cargo.toml new file mode 100644 index 00000000..792d7fc9 --- /dev/null +++ b/decimal-macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "decimal-macros" +version = "0.2.0" +authors = ["Callum Tolley "] + +[lib] +name = "decimal_macros" +proc-macro = true + +[dependencies] +decimal = { path = "../" } +libc = "~0.2" +quote = "0.6" +proc-macro2 = "0.4" + +[build-dependencies] +gcc = "0.3" diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs new file mode 100644 index 00000000..e771627b --- /dev/null +++ b/decimal-macros/src/lib.rs @@ -0,0 +1,43 @@ +extern crate decimal; +extern crate proc_macro; +#[macro_use] +extern crate quote; +extern crate proc_macro2; + +use proc_macro::TokenStream; +use std::str::FromStr; + +#[proc_macro] +pub fn d128(input: TokenStream) -> TokenStream { + let source = input.to_string(); + let source = source.replace(" ", ""); + let source = source.replace("_", ""); + + let d = match decimal::d128::from_str(&source[..]) { + Ok(d) => d, + Err(e) => panic!("Unexpected decimal format for {}: {:?}", source, e), + }; + let bytes: [u8; 16] = d.as_bytes(); + let iter = bytes.iter(); + let expanded = quote! { + ::decimal::d128::from_raw_bytes([ #(#iter,)* ]) + }; + expanded.into() +} + +#[proc_macro] +pub fn d64(input: TokenStream) -> TokenStream { + let source = input.to_string(); + let source = source.replace(" ", ""); + let source = source.replace("_", ""); + let d = match decimal::d64::from_str(&source[..]) { + Ok(d) => d, + Err(e) => panic!("Unexpected decimal format for {}: {:?}", source, e), + }; + let bytes: [u8; 8] = d.as_bytes(); + let iter = bytes.iter(); + let expanded = quote! { + ::decimal::d64::from_raw_bytes([ #(#iter,)* ]) + }; + expanded.into() +} diff --git a/decimal-macros/src/plugin.rs b/decimal-macros/src/plugin.rs new file mode 100644 index 00000000..22d6c093 --- /dev/null +++ b/decimal-macros/src/plugin.rs @@ -0,0 +1,252 @@ +//#![feature(plugin_registrar, rustc_private, const_let, const_fn)] +#![crate_name = "decimal_macros"] +#![crate_type = "dylib"] +#![feature(plugin_registrar)] +#![feature(rustc_private)] + +extern crate libc; +extern crate decimal; + +extern crate rustc_plugin; +extern crate syntax; + +use rustc_plugin::Registry; +use syntax::ext::base::{DummyResult, MacEager, ExtCtxt, MacResult}; +#[allow(unused_imports)] +use syntax::ext::build::AstBuilder; +use syntax::ext::source_util; +use syntax::source_map::Span; // changes after nightly-2018-08-06 +//use syntax::codemap::Span; +use syntax::ast::{ExprKind, LitKind, StrStyle}; +use syntax::tokenstream::TokenTree; + +use decimal::{d128, d64}; + +// pub use self::d128_lit::*; +// pub use self::d64_lit::*; + +/* + * just putting this here for posterity + * + +#[macro_export] +/// A macro to construct d128 literals. +/// +/// # Examples: +/// ``` +/// # #[macro_use] +/// # extern crate decimal; +/// # use decimal::d128; +/// # use std::str::FromStr; +/// # fn main() { +/// assert!(d128!(NaN).is_nan()); +/// assert!(d128!(0).is_zero()); +/// assert!(d128!(-0.1).is_negative()); +/// assert_eq!(d128!(1.2345), d128::from_str("1.2345").unwrap()); +/// // underscore separators work, too +/// assert_eq!(d128!(1_000_000), d128::from_str("1000000").unwrap()); +/// # } +/// ``` +macro_rules! d128 { + ($lit:expr) => {{ + use std::str::FromStr; + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d128::from_str(&clean).expect("Invalid decimal float literal") + }} +} + +#[macro_export] +/// A macro to construct d64 literals. +/// +/// # Examples: +/// ``` +/// # #[macro_use] +/// # extern crate decimal; +/// # use decimal::d64; +/// # use std::str::FromStr; +/// # fn main() { +/// assert!(d64!(NaN).is_nan()); +/// assert!(d64!(0).is_zero()); +/// assert!(d64!(-0.1).is_negative()); +/// assert_eq!(d64!(1.2345), d64::from_str("1.2345").unwrap()); +/// // underscore separators work, too +/// assert_eq!(d64!(1_000_000), d64::from_str("1000000").unwrap()); +/// # } +/// ``` +macro_rules! d64 { + ($lit:expr) => {{ + use std::str::FromStr; + $crate::d64::from_str(stringify!($lit)).expect("Invalid decimal float literal") + }} +} +*/ + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_macro("d64", self::d64_lit::d64_lit); + reg.register_macro("d128", self::d128_lit::d128_lit); +} + +mod d128_lit { + use super::*; + + pub(crate) fn d128_lit<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box { + // Turn input into a string literal + // e.g. d128!(0.1) -> "0.1", d128!(NaN) -> "NaN" + let mac_res = source_util::expand_stringify(cx, sp, tts); + + let ex = match mac_res.make_expr() { + Some(ex) => ex, + None => { + // I don't know when this occurs + cx.span_err(sp, "one argument needed"); + return DummyResult::any(sp); + } + }; + + match &ex.node { + &ExprKind::Lit(ref lit) => match &lit.node { + &LitKind::Str(ref s, StrStyle::Cooked) => { + // Check for empty argument + if s.as_str().len() == 0 { + cx.span_err(sp, "one argument needed"); + return DummyResult::any(sp); + } + + // remove underscore separators + let clean: String = s.as_str().replace("_", ""); + + let num = match d128_from_str(&clean) { + Ok(num) => num, + Err(s) => { + cx.span_err(lit.span, s); + return DummyResult::any(sp); + } + }; + + let num = num.as_bytes(); + + // Create array literal + let mut vec = Vec::new(); + for i in 0..16 { + vec.push(cx.expr_u8(lit.span, num[i])); + } + let vec = cx.expr_vec(lit.span, vec); + let ids = vec![cx.ident_of("decimal"), cx.ident_of("d128"), cx.ident_of("from_raw_bytes")]; + let ex = cx.expr_call_global(lit.span, ids, vec![vec]); + + return MacEager::expr(ex); + }, + _ => {} + }, + _ => {} + } + // This should never happen. + cx.span_err(sp, "not a valid d128 number"); + DummyResult::any(sp) + } + + fn d128_from_str(s: &str) -> Result { + use std::str::FromStr; + d128::set_status(decimal::Status::empty()); + let no_spaces = s.replace(" ", ""); + let res = d128::from_str(&no_spaces); + + let status = d128::get_status(); + if status.contains(decimal::Status::CONVERSION_SYNTAX) { + println!("{} {:?}", s, res); + Err("not a valid d128 number (CONVERSION_SYNTAX)") + } else if status.contains(decimal::Status::OVERFLOW) { + Err("too large for a d128 number (OVERFLOW)") + } else if status.contains(decimal::Status::UNDERFLOW) { + Err("too small for a d128 number (UNDERFLOW)") + } else if !status.is_empty() { + Err("not a valid d128 number (is_empty)") + } else { + Ok(res.unwrap()) + } + } + +} + +mod d64_lit { + use super::*; + + pub(crate) fn d64_lit<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box { + // Turn input into a string literal + // e.g. d64!(0.1) -> "0.1", d64!(NaN) -> "NaN" + let mac_res = source_util::expand_stringify(cx, sp, tts); + + let ex = match mac_res.make_expr() { + Some(ex) => ex, + None => { + // I don't know when this occurs + cx.span_err(sp, "one argument needed"); + return DummyResult::any(sp); + } + }; + + match &ex.node { + &ExprKind::Lit(ref lit) => match &lit.node { + &LitKind::Str(ref s, StrStyle::Cooked) => { + // Check for empty argument + if s.as_str().len() == 0 { + cx.span_err(sp, "one argument needed"); + return DummyResult::any(sp); + } + + // remove underscore separators + let clean: String = s.as_str().replace("_", ""); + + let num = match d64_from_str(&clean) { + Ok(num) => num, + Err(s) => { + cx.span_err(lit.span, s); + return DummyResult::any(sp); + } + }; + + let num = num.as_bytes(); + + // Create array literal + let mut vec = Vec::new(); + for i in 0..8 { + vec.push(cx.expr_u8(lit.span, num[i])); + } + let vec = cx.expr_vec(lit.span, vec); + let ids = vec![cx.ident_of("decimal"), cx.ident_of("d64"), cx.ident_of("from_raw_bytes")]; + let ex = cx.expr_call_global(lit.span, ids, vec![vec]); + + return MacEager::expr(ex); + }, + _ => {} + }, + _ => {} + } + // This should never happen. + cx.span_err(sp, "not a valid d64 number"); + DummyResult::any(sp) + } + + fn d64_from_str(s: &str) -> Result { + use std::str::FromStr; + d64::set_status(decimal::Status::empty()); + let no_spaces = s.replace(" ", ""); + let res = d64::from_str(&no_spaces); + + let status = d64::get_status(); + if status.contains(decimal::Status::CONVERSION_SYNTAX) { + println!("{} {:?}", s, res); + Err("not a valid d64 number (CONVERSION_SYNTAX)") + } else if status.contains(decimal::Status::OVERFLOW) { + Err("too large for a d64 number (OVERFLOW)") + } else if status.contains(decimal::Status::UNDERFLOW) { + Err("too small for a d64 number (UNDERFLOW)") + } else if !status.is_empty() { + Err("not a valid d64 number (is_empty)") + } else { + Ok(res.unwrap()) + } + } +} diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs new file mode 100644 index 00000000..dbb06f5c --- /dev/null +++ b/decimal-macros/tests/test.rs @@ -0,0 +1,37 @@ +#![feature(proc_macro_hygiene)] +#![feature(const_fn)] +#![allow(unused)] + +extern crate decimal_macros; +extern crate decimal; + +use decimal::d128; +use decimal_macros::*; + +#[test] +fn basic_plugin_sanity_checks() { + let a = d128!(0.1); + let b = d128!(0.2); + let c = d128!(0.3); + let res = a + b; + let eq = res == c; + if eq { + println!("{} + {} = {}", a, b, res); + } else { + println!("{} + {} = {} (expected {})", a, b, res, c); + } + assert!(eq); + + assert_eq!(d128!(0.1), decimal::d128::from(1) / decimal::d128::from(10)); +} + +#[test] +fn zero_eq_zero() { + assert_eq!(d128!(0), decimal::d128::zero()); +} + +#[test] +fn create_d128_const() { + const ZERO: decimal::d128 = { d128!(0) }; + assert_eq!(ZERO, decimal::d128::zero()); +} diff --git a/justfile b/justfile new file mode 100644 index 00000000..740c0eff --- /dev/null +++ b/justfile @@ -0,0 +1,2 @@ +bench PATTERN='': + RUSTFLAGS="-C target-cpu=native" cargo bench diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 00000000..eedd153e --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,41 @@ +extern crate decimal; +extern crate clap; +use std::str::FromStr; + +fn main() { + let args: clap::ArgMatches = clap::App::new("bytes") + .version("0.1.1") + .setting(clap::AppSettings::AllowLeadingHyphen) + .arg(clap::Arg::with_name("dec_literal") + .help("Decimal float literal to show bytes for") + .required(true)) + .arg(clap::Arg::with_name("f64") + .help("Show bytes for the f64 representation of the number, instead of d128") + .short("d") + .long("f64") + .alias("double") + .alias("float64")) + .arg(clap::Arg::with_name("f32") + .help("Show bytes for the f32 representation of the number, instead of d128") + .short("f") + .long("f32") + .alias("float") + .alias("float32")) + .get_matches(); + + let literal = args.value_of("dec_literal").unwrap(); + + if args.is_present("f64") { + let f = f64::from_str(&literal).expect("Invalid float literal"); + println!("Bytes for {} (f64)", f); + println!("{:?}", unsafe { ::std::mem::transmute::(f) }); + } else if args.is_present("f32") { + let f = f32::from_str(&literal).expect("Invalid float literal"); + println!("Bytes for {} (f32)", f); + println!("{:?}", unsafe { ::std::mem::transmute::(f) }); + } else { + let d = decimal::d128::from_str(&literal).expect("Invalid float literal"); + println!("Bytes for {}", d); + println!("{:?}", d.to_raw_bytes()); + } +} diff --git a/src/dec128.rs b/src/dec128.rs index e7a37c48..f9965fec 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -2,7 +2,7 @@ use super::Class; use super::Status; use super::Rounding; -use context::*; +use crate::context::*; use libc::{c_char, int32_t, uint8_t, uint32_t}; #[cfg(feature = "ord_subset")] use ord_subset; @@ -10,6 +10,9 @@ use ord_subset; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; #[cfg(feature = "serde")] use serde; +#[cfg(feature = "slog")] +use slog; +use std::mem::MaybeUninit; use std::borrow::Borrow; use std::cell::RefCell; use std::default::Default; @@ -17,15 +20,17 @@ use std::ffi::{CStr, CString}; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter::Sum; -use std::mem::uninitialized; use std::num::FpCategory; use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Rem, RemAssign, Neg, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, - ShlAssign, Shr, ShrAssign}; + ShlAssign, Shr, ShrAssign, Deref}; use std::str::FromStr; use std::str::from_utf8_unchecked; +use std::{f32, f64}; thread_local!(static CTX: RefCell = RefCell::new(d128::default_context())); +thread_local!(static ROUND_DOWN: RefCell = RefCell::new(d128::with_rounding(Rounding::Down))); +thread_local!(static HALF_UP: RefCell = RefCell::new(d128::with_rounding(Rounding::HalfUp))); #[repr(C)] #[derive(Clone, Copy)] @@ -60,16 +65,16 @@ impl ord_subset::OrdSubset for d128 { } #[cfg(feature = "ord_subset")] -impl Into> for d128 { - fn into(self) -> ord_subset::OrdVar { - ord_subset::OrdVar::new(self) +impl From for ord_subset::OrdVar { + fn from(val: d128) -> ord_subset::OrdVar { + ord_subset::OrdVar::new(val) } } #[cfg(feature = "rustc-serialize")] impl Decodable for d128 { fn decode(d: &mut D) -> Result { - let s = try!(d.read_str()); + let s = r#try!(d.read_str()); Ok(Self::from_str(&s).expect("unreachable")) } } @@ -127,10 +132,11 @@ impl<'de> serde::de::Visitor<'de> for d128Visitor { /// Converts an i32 to d128. The result is exact and no error is possible. impl From for d128 { + #[inline] fn from(val: i32) -> d128 { unsafe { - let mut res: d128 = uninitialized(); - *decQuadFromInt32(&mut res, val) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadFromInt32(res.as_mut_ptr(), val) } } } @@ -139,16 +145,199 @@ impl From for d128 { impl From for d128 { fn from(val: u32) -> d128 { unsafe { - let mut res: d128 = uninitialized(); - *decQuadFromUInt32(&mut res, val) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadFromUInt32(res.as_mut_ptr(), val) } } } +impl From for d128 { + fn from(x: f64) -> d128 { + d128::from_str(&format!("{}", x)).unwrap() + } +} + +impl From for f64 { + #[inline] + fn from(x: d128) -> Self { + if !x.is_finite() { + if x.is_infinite() { + if x > d128::zero() { + f64::INFINITY + } else { + f64::NEG_INFINITY + } + } else { + f64::NAN + } + } else { + f64::from_str(&(format!("{}", x))) + .unwrap_or(f64::NAN) + } + } +} + +impl From for f32 { + #[inline] + fn from(x: d128) -> Self { + if !x.is_finite() { + if x.is_infinite() { + if x > d128::zero() { + f32::INFINITY + } else { + f32::NEG_INFINITY + } + } else { + f32::NAN + } + } else { + // for whatever reason, the f32::from_str is slow + f64::from_str(&format!("{}", x)) + .unwrap_or(f64::NAN) + as f32 + } + } +} + +/// Create a `u64` from a `d128`, which is assumed to be > 0. +/// +/// # Examples +/// ``` +/// #![feature(proc_macro_hygiene)] +/// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; +/// +/// fn main() { +/// let x = d128!(12345); +/// assert_eq!(u64::from(x), 12345u64); +/// } +/// ``` +impl From for u64 { + #[inline] + fn from(val: d128) -> u64 { + debug_assert!(val >= d128::zero()); + //let mut bcd = [0; 34]; + let mut bcd: [u8; 34] = [0u8; 34]; + let mut exp: i32 = 0; + unsafe { + let _ = decQuadToBCD(&val, &mut exp, &mut bcd); + //debug_assert_eq!(i, 0); + } + let mut u: u128 = 0; + let mut i: usize = 33; + let n: usize = val.digits() as usize; + //assert!(n < 21, "val.digits() = {} (> 21); val = {}", n, val); + while i > 33 - n { + u += bcd[i] as u128 * 10u128.pow((33 - i) as u32); + i -= 1; + } + // for (i, b) in bcd.iter().rev().enumerate().take(val.digits() as usize) { + // u += (*b as u64) * 10u64.pow(i as u32); + // } + //println!("exp = {}", exp); + match exp { + e if e > 0 => (u * 10u128.pow(exp as u32)) as u64, + + e if e < 0 => (u / 10u128.pow(exp.abs() as u32)) as u64, + + _ => u as u64 + } + // if exp > 0 { + // u *= 10u64.pow(exp as u32); + // } else if exp < 0 { + // u /= 10u64.pow(exp.abs() as u32) + // } + // u + //(u * exp as i64) as u64 + // bcd.iter() + // .rev() + // .enumerate() + // .take(val.digits() as usize) + // .fold(0, |acc, (i, x)| acc + (*x as u64 ) * 10u64.pow(i as u32)) + } +} + +/// Create a `u128` from a `d128`, which is assumed to be > 0. +/// +/// # Examples +/// ``` +/// #![feature(proc_macro_hygiene)] +/// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; +/// +/// fn main() { +/// let x = d128!(12345); +/// assert_eq!(u128::from(x), 12345u128); +/// } +/// ``` +impl From for u128 { + #[inline] + fn from(val: d128) -> u128 { + debug_assert!(val >= d128::zero()); + let n: usize; + let r: d128; + const ONE: d128 = d128::from_raw_bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 34]); + debug_assert_eq!(ONE, d128::from_str("1").unwrap()); + match val.digits() { + x @ 0 ..= 33 => { + n = x as usize; + r = val; + } + + _ => { + r = val.truncate(ONE); + n = r.digits() as usize; + } + } + debug_assert!(n < 34); + + //let mut bcd = [0; 34]; + let mut bcd: [u8; 34] = [0; 34]; + let mut exp: i32 = 0; + unsafe { + let _ = decQuadToBCD(&r, &mut exp, &mut bcd); + //debug_assert_eq!(i, 0); + } + let mut u: u128 = 0; + let mut i: usize = 33; + let thresh: usize = 33usize.saturating_sub(n); + //assert!(n < 21, "val.digits() = {} (> 21); val = {}", n, val); + while i > thresh { + u += bcd[i] as u128 * 10u128.pow((33usize.saturating_sub(i)) as u32); + i -= 1; + } + // for (i, b) in bcd.iter().rev().enumerate().take(val.digits() as usize) { + // u += (*b as u64) * 10u64.pow(i as u32); + // } + //println!("exp = {}", exp); + match exp { + e if e > 0 => (u * 10u128.pow(exp as u32)), + + e if e < 0 => (u / 10u128.pow(exp.abs() as u32)), + + _ => u + } + // if exp > 0 { + // u *= 10u64.pow(exp as u32); + // } else if exp < 0 { + // u /= 10u64.pow(exp.abs() as u32) + // } + // u + //(u * exp as i64) as u64 + // bcd.iter() + // .rev() + // .enumerate() + // .take(val.digits() as usize) + // .fold(0, |acc, (i, x)| acc + (*x as u64 ) * 10u64.pow(i as u32)) + } +} + /// Converts an u64 to d128. The result is exact and no error is possible. impl From for d128 { fn from(mut val: u64) -> d128 { - let mut bcd = [0; 34]; + let mut bcd = [0u8; 34]; let mut i = 33; while val > 0 { bcd[i] = (val % 10) as u8; @@ -156,8 +345,59 @@ impl From for d128 { i -= 1; } unsafe { - let mut res: d128 = uninitialized(); - *decQuadFromBCD(&mut res, 0, bcd.as_ptr(), 0) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadFromBCD(res.as_mut_ptr(), 0, bcd.as_ptr(), 0) + } + } +} + +/// Converts an u128 to d128. The result is exact and no error is possible. +/// +/// # Examples +/// ``` +/// #![feature(proc_macro_hygiene)] +/// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; +/// use decimal::d128; +/// +/// fn main() { +/// let a: u128 = 12345; +/// assert_eq!(d128::from(a), d128!(12345)); +/// +/// let b: u128 = 340282366920938463463374607431768211455; +/// assert_eq!(d128::from(b), d128!(340282366920938463463374607431768211455)); +/// +/// let c: u128 = 999_999_999_999_999_999_999_999_999_999_999; +/// assert_eq!(d128::from(c), d128!(999999999999999999999999999999999)); +/// +/// let d: u128 = 9_999_999_999_999_999_999_999_999_999_999_999; +/// assert_eq!(d128::from(d), d128!(9999999999999999999999999999999999)); +/// +/// let e: u128 = 98_999_999_999_999_999_999_999_999_999_999_999; +/// assert_eq!(d128::from(e), d128!(98999999999999999999999999999999999)); +/// +/// let f: u128 = 10_000_000_000_000_000_000_000_000_000_000_000; +/// assert_eq!(d128::from(f), d128!(10000000000000000000000000000000000)); +/// } +/// ``` +impl From for d128 { + fn from(mut val: u128) -> d128 { + if val > 9_999_999_999_999_999_999_999_999_999_999_999 { // max value w/ 34 digits + return d128::from_str(&format!("{}", val)).unwrap() + } + + let mut bcd = [0; 34]; + let mut i = 0; + while val > 0 && i < 34 { + bcd[33 - i] = (val % 10) as u8; + val /= 10; + i += 1; + } + + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadFromBCD(res.as_mut_ptr(), 0, bcd.as_ptr(), 0) } } } @@ -179,6 +419,14 @@ impl AsRef for d128 { } } +impl Deref for d128 { + type Target = [uint8_t; 16]; + + fn deref(&self) -> &[uint8_t; 16] { + &self.bytes + } +} + /// Converts a string to d128. The length of the coefficient and the size of the exponent are /// checked by this routine, so rounding will be applied if necessary, and this may set status /// flags (`UNDERFLOW`, `OVERFLOW`) will be reported, or rounding applied, as necessary. There is @@ -194,27 +442,27 @@ impl FromStr for d128 { Ok(cstr) => cstr, }; d128::with_context(|ctx| { - let mut res: d128; - unsafe { - res = uninitialized(); - decQuadFromString(&mut res, cstr.as_ptr(), ctx); - } - Ok(res) + let out: d128 = unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + decQuadFromString(res.as_mut_ptr(), cstr.as_ptr(), ctx); + res.assume_init() + }; + Ok(out) }) } } /// Converts this d128 to an i32. It uses Rounding::HalfEven. -impl Into for d128 { - fn into(self) -> i32 { - d128::with_context(|ctx| unsafe { decQuadToInt32(&self, ctx, ctx.rounding) }) +impl From for i32 { + fn from(val: d128) -> i32 { + d128::with_context(|ctx| unsafe { decQuadToInt32(&val, ctx, ctx.rounding) }) } } /// Converts this d128 to an u32. It uses Rounding::HalfEven. -impl Into for d128 { - fn into(self) -> u32 { - d128::with_context(|ctx| unsafe { decQuadToUInt32(&self, ctx, ctx.rounding) }) +impl From for u32 { + fn from(val: d128) -> u32 { + d128::with_context(|ctx| unsafe { decQuadToUInt32(&val, ctx, ctx.rounding) }) } } @@ -259,7 +507,7 @@ impl fmt::LowerExp for d128 { impl fmt::LowerHex for d128 { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { for b in self.bytes.iter().rev() { - try!(write!(fmt, "{:02x}", b)); + r#try!(write!(fmt, "{:02x}", b)); } Ok(()) } @@ -302,7 +550,10 @@ macro_rules! ffi_unary_op { fn $method(self) -> $t { $t::with_context(|ctx| { - unsafe { let mut res: $t = uninitialized(); *$ffi(&mut res, self, ctx)} + unsafe { + let mut res: MaybeUninit<$t> = MaybeUninit::uninit(); + *$ffi(res.as_mut_ptr(), self, ctx) + } }) } } @@ -347,7 +598,10 @@ macro_rules! ffi_binary_op { fn $method(self, other: &'a $t) -> $t { $t::with_context(|ctx| { - unsafe { let mut res: $t = uninitialized(); *$ffi(&mut res, self, other, ctx) } + unsafe { + let mut res: MaybeUninit<$t> = MaybeUninit::uninit(); + *$ffi(res.as_mut_ptr(), self, other, ctx) + } }) } } @@ -421,8 +675,8 @@ impl<'a> Shl for &'a d128 { let shift = d128::from(amount as u32); d128::with_context(|ctx| { unsafe { - let mut res: d128 = uninitialized(); - *decQuadShift(&mut res, self, &shift, ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadShift(res.as_mut_ptr(), self, &shift, ctx) } }) } @@ -460,8 +714,8 @@ impl<'a> Shr for &'a d128 { let shift = -d128::from(amount as u32); d128::with_context(|ctx| { unsafe { - let mut res: d128 = uninitialized(); - *decQuadShift(&mut res, self, &shift, ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadShift(res.as_mut_ptr(), self, &shift, ctx) } }) } @@ -489,11 +743,34 @@ impl Sum for d128 where T: Borrow { impl d128 { fn default_context() -> Context { unsafe { - let mut res: Context = uninitialized(); - *decContextDefault(&mut res, 128) + let mut res: MaybeUninit = MaybeUninit::uninit(); + let out = decContextDefault(res.as_mut_ptr(), 128); + *out + } + } + + /// Initialize a `Context` with the specified `Rounding`. + fn with_rounding(rounding: Rounding) -> Context { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + let mut ctx = *decContextDefault(res.as_mut_ptr(), 128); + decContextSetRounding(&mut ctx, rounding as u32); + ctx } } + fn with_round_down(f: F) -> R + where F: FnOnce(&mut Context) -> R + { + ROUND_DOWN.with(|ctx| f(&mut ctx.borrow_mut())) + } + + fn with_half_up(f: F) -> R + where F: FnOnce(&mut Context) -> R + { + HALF_UP.with(|ctx| f(&mut ctx.borrow_mut())) + } + fn with_context(f: F) -> R where F: FnOnce(&mut Context) -> R { @@ -501,8 +778,9 @@ impl d128 { } /// Creates a d128 from raw bytes. Endianess is host dependent. - pub unsafe fn from_raw_bytes(bytes: [u8; 16]) -> d128 { - d128 { bytes: bytes } + #[cfg(target_endian = "little")] + pub const fn from_raw_bytes(bytes: [u8; 16]) -> d128 { + d128 { bytes } } /// Returns raw bytes for this d128. Endianess is host dependent. @@ -510,6 +788,45 @@ impl d128 { self.bytes } + /// Returns `self.to_raw_bytes()`. I prefer this name. + pub fn as_bytes(&self) -> [u8; 16] { self.to_raw_bytes() } + + /// For benchmark comparisons. + /// + #[cfg(test)] + #[inline] + fn baseline_from_f64_lossy(x: f64) -> d128 { + d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() + } + + /// Multiplies `x` by `1e8`, truncates to integer, converts to `d128`, then + /// scales back down. Benches at ~200ns. A bit faster than using `FromStr` + /// at the cost of precision. Precision loss is much worse for larger numbers. + /// For the range `(-1000, 1000)` this approach is tested to return within + /// `1e-4` of the original value. + #[cfg(target_endian = "little")] + #[inline] + pub fn from_f64_lossy(x: f64) -> d128 { + const SCALE: d128 = + d128::from_raw_bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 34]); + debug_assert_eq!(SCALE, d128::from_str("1e-8").unwrap()); + d128::from((x * 1e8) as i64) * SCALE + } + + /// Multiplies `x` by `1e8`, truncates to integer, converts to `d128`, then + /// scales back down. Benches at ~200ns. A bit faster than using `FromStr` + /// at the cost of precision. Precision loss is much worse for larger numbers. + /// For the range `(-1000, 1000)` this approach is tested to return within + /// `1e-4` of the original value. + #[cfg(target_endian = "little")] + #[inline] + pub fn from_f32_lossy(x: f32) -> d128 { + const SCALE: d128 = + d128::from_raw_bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 34]); + debug_assert_eq!(SCALE, d128::from_str("1e-8").unwrap()); + d128::from((x * 1e8) as i64) * SCALE + } + /// Returns the thread local status. pub fn get_status() -> Status { d128::with_context(|ctx| Status::from_bits_truncate(ctx.status)) @@ -527,7 +844,7 @@ impl d128 { Self::from_str("qNaN").unwrap() } else { unsafe { - let mut res: d128 = uninitialized(); + let mut res: d128 = MaybeUninit::zeroed().assume_init(); for (i, octet) in s.as_bytes().chunks(2).rev().enumerate() { res.bytes[i] = match u8::from_str_radix(from_utf8_unchecked(octet), 16) { Ok(val) => val, @@ -544,19 +861,21 @@ impl d128 { /// Returns the d128 representing +0. pub fn zero() -> d128 { unsafe { - let mut res = uninitialized(); - *decQuadZero(&mut res) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadZero(res.as_mut_ptr()) } } /// Returns the d128 representing +Infinity. - pub fn infinity() -> d128 { - d128!(Infinity) + #[cfg(target_endian = "little")] + pub const fn infinity() -> d128 { + d128::from_raw_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120]) } /// Returns the d128 representing -Infinity. - pub fn neg_infinity() -> d128 { - d128!(-Infinity) + #[cfg(target_endian = "little")] + pub const fn neg_infinity() -> d128 { + d128::from_raw_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248]) } // Computational. @@ -623,10 +942,12 @@ impl d128 { /// has an integral value in the range –1999999997 through +999999999. pub fn pow>(mut self, exp: O) -> d128 { d128::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); - let mut num_exp: decNumber = uninitialized(); - decimal128ToNumber(&self, &mut num_self); - decimal128ToNumber(exp.as_ref(), &mut num_exp); + let mut num_self : MaybeUninit = MaybeUninit::uninit(); + let mut num_exp : MaybeUninit = MaybeUninit::uninit(); + decimal128ToNumber(&self, num_self.as_mut_ptr()); + decimal128ToNumber(exp.as_ref(), num_exp.as_mut_ptr()); + let mut num_self = num_self.assume_init(); + let num_exp = num_exp.assume_init(); decNumberPower(&mut num_self, &num_self, &num_exp, ctx); *decimal128FromNumber(&mut self, &num_self, ctx) }) @@ -639,10 +960,12 @@ impl d128 { /// 106 restrictions on precision and range apply as described above. pub fn exp>(mut self, exp: O) -> d128 { d128::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); - let mut num_exp: decNumber = uninitialized(); - decimal128ToNumber(&self, &mut num_self); - decimal128ToNumber(exp.as_ref(), &mut num_exp); + let mut num_self : MaybeUninit = MaybeUninit::uninit(); + let mut num_exp : MaybeUninit = MaybeUninit::uninit(); + decimal128ToNumber(&self, num_self.as_mut_ptr()); + decimal128ToNumber(exp.as_ref(), num_exp.as_mut_ptr()); + let mut num_self = num_self.assume_init(); + let num_exp = num_exp.assume_init(); decNumberExp(&mut num_self, &num_self, &num_exp, ctx); *decimal128FromNumber(&mut self, &num_self, ctx) }) @@ -656,8 +979,9 @@ impl d128 { /// apply as described above. pub fn ln(mut self) -> d128 { d128::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); - decimal128ToNumber(&self, &mut num_self); + let mut num_self : MaybeUninit = MaybeUninit::uninit(); + decimal128ToNumber(&self, num_self.as_mut_ptr()); + let mut num_self = num_self.assume_init(); decNumberLn(&mut num_self, &num_self, ctx); *decimal128FromNumber(&mut self, &num_self, ctx) }) @@ -671,8 +995,9 @@ impl d128 { /// precision and range apply as described above. pub fn log10(mut self) -> d128 { d128::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); - decimal128ToNumber(&self, &mut num_self); + let mut num_self: MaybeUninit = MaybeUninit::uninit(); + decimal128ToNumber(&self, num_self.as_mut_ptr()); + let mut num_self = num_self.assume_init(); decNumberLog10(&mut num_self, &num_self, ctx); *decimal128FromNumber(&mut self, &num_self, ctx) }) @@ -694,10 +1019,70 @@ impl d128 { /// Returns `self` set to have the same quantum as `other`, if possible (that is, numerically /// the same value but rounded or padded if necessary to have the same exponent as `other`, for /// example to round a monetary quantity to cents). + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d128!(0.1); + /// assert_eq!(d128!(0.400012342423).quantize(prec), d128!(0.4)); + /// // uses default rounding (half even) + /// assert_eq!(d128!(0.05).quantize(prec), d128!(0.0)); + /// assert_eq!(d128!(0.15).quantize(prec), d128!(0.2)); + /// } + /// ``` pub fn quantize>(mut self, other: O) -> d128 { d128::with_context(|ctx| unsafe { *decQuadQuantize(&mut self, &self, other.as_ref(), ctx) }) } + /// Like `quantize`, but uses `Rounding::Down`. + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d128!(0.1); + /// assert_eq!(d128!(0.05).truncate(prec), d128!(0.0)); + /// assert_eq!(d128!(0.15).truncate(prec), d128!(0.1)); + /// assert_eq!(d128!(0.19).truncate(prec), d128!(0.1)); + /// } + /// ``` + pub fn truncate>(mut self, other: O) -> d128 { + d128::with_round_down(|ctx| unsafe { *decQuadQuantize(&mut self, &self, other.as_ref(), ctx) }) + } + + /// Like `quantize`, but uses `Rounding::HalfUp`. + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d128!(0.1); + /// assert_eq!(d128!(0.15).round(prec), d128!(0.2)); + /// assert_eq!(d128!(0.14999999999).round(prec), d128!(0.1)); + /// assert_eq!(d128!(0.19).round(prec), d128!(0.2)); + /// assert_eq!(d128!(0.05).round(prec), d128!(0.1)); + /// } + /// ``` + pub fn round>(mut self, other: O) -> d128 { + d128::with_half_up(|ctx| unsafe { *decQuadQuantize(&mut self, &self, other.as_ref(), ctx) }) + } + /// Returns a copy of `self` with its coefficient reduced to its shortest possible form without /// changing the value of the result. This removes all possible trailing zeros from the /// coefficient (some may remain when the number is very close to the most positive or most @@ -713,6 +1098,20 @@ impl d128 { /// finite integer (with exponent=0) in the range -34 through +34. NaNs are propagated as /// usual. If `self` is infinite the result is Infinity of the same sign. No status is set /// unless `amount` is invalid or an operand is an sNaN. + /// + /// # Examples + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let x = d128!(1.2345); + /// let one = d128!(1); + /// assert_eq!(x.rotate(one), d128!(12.345)); + /// } + /// ``` pub fn rotate>(mut self, amount: O) -> d128 { d128::with_context(|ctx| unsafe { *decQuadRotate(&mut self, &self, amount.as_ref(), ctx) }) } @@ -732,8 +1131,8 @@ impl d128 { /// only if `self` or `other` is a NaN. pub fn compare>(&self, other: O) -> d128 { d128::with_context(|ctx| unsafe { - let mut res: d128 = uninitialized(); - *decQuadCompare(&mut res, self, other.as_ref(), ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadCompare(res.as_mut_ptr(), self, other.as_ref(), ctx) }) } @@ -742,8 +1141,8 @@ impl d128 { /// Infinity and NaN). The result will be –1, 0, or 1. pub fn compare_total>(&self, other: O) -> d128 { d128::with_context(|ctx| unsafe { - let mut res: d128 = uninitialized(); - *decQuadCompareTotal(&mut res, self, other.as_ref(), ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decQuadCompareTotal(res.as_mut_ptr(), self, other.as_ref(), ctx) }) } @@ -850,13 +1249,49 @@ impl d128 { pub fn is_zero(&self) -> bool { unsafe { decQuadIsZero(self) != 0 } } + + pub fn finite_or(self, default: d128) -> d128 { + match self.is_finite() { + true => self, + false => default + } + } + + pub fn finite_or_else d128>(self, f: F) -> d128 { + match self.is_finite() { + true => self, + false => f() + } + } + + pub fn finite_or_err(self, err: E) -> Result { + if self.is_finite() { + Ok(self) + } else { + Err(err) + } + } +} + +#[cfg(feature = "slog")] +impl slog::Value for d128 { + fn serialize( + &self, + _record: &slog::Record, + key: slog::Key, + serializer: &mut dyn slog::Serializer, + ) -> Result<(), slog::Error> { + serializer.emit_arguments(key, &format_args!("{}", self)) + } } extern "C" { // Context. fn decContextDefault(ctx: *mut Context, kind: uint32_t) -> *mut Context; + fn decContextSetRounding(ctx: *mut Context, rounding: uint32_t); // Utilities and conversions, extractors, etc. fn decQuadFromBCD(res: *mut d128, exp: i32, bcd: *const u8, sign: i32) -> *mut d128; + fn decQuadToBCD(res: *const d128, exp: &mut i32, bcd: &mut [u8; 34]) -> int32_t; fn decQuadFromInt32(res: *mut d128, src: int32_t) -> *mut d128; fn decQuadFromString(res: *mut d128, s: *const c_char, ctx: *mut Context) -> *mut d128; fn decQuadFromUInt32(res: *mut d128, src: uint32_t) -> *mut d128; @@ -982,6 +1417,7 @@ extern "C" { -> *mut decNumber; } +#[allow(unused)] #[cfg(test)] mod tests { #[cfg(any(feature = "ord_subset", feature = "rustc-serialize"))] @@ -998,6 +1434,616 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; + use rand::{self, Rng}; + use rand::distributions::{IndependentSample, Range}; + + #[allow(unused_imports)] + use test::{black_box, Bencher}; + + macro_rules! d128 { + ($lit:expr) => {{ + use std::str::FromStr; + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d128::from_str(&clean).expect("Invalid decimal float literal") + }} + } + + #[test] + fn d128_to_f32_fuzz() { + assert!(f32::from(d128!(NaN)).is_nan()); + assert!(d128!(Inf).is_infinite()); + assert!(d128!(Inf) > d128::zero()); + let inf = f32::from(d128!(Inf)); + assert!(inf.is_infinite(), "expected inf, val: {}", inf); + } + + #[test] + fn test_d128_to_f64_approx() { + let a = 1.23456789f64; + assert_relative_eq!(a, f64::from(d128!(1.23456789)), epsilon = 1e-6f64); + let a = 4000.2340293842; + assert_relative_eq!(a, f64::from(d128!(4000.2340293842)), epsilon = 1e-6f64); + } + + #[test] + fn test_d128_to_f32_approx() { + let a = 1.23456789f32; + assert_relative_eq!(a, f32::from(d128!(1.23456789)), epsilon = 1e-6f32); + let a = 4000.2340293842; + assert_relative_eq!(a, f32::from(d128!(4000.2340293842)), epsilon = 1e-6f32); + } + + #[test] + fn verify_from_f64_for_d128_behaves_well() { + let test = |x: &str| { + let a: f64 = f64::from_str(x).unwrap(); + let b: d128 = d128::from_str(x).unwrap(); + let c = d128::from(a); + let epsilon = d128!(1e-6); + assert!((b - c).abs() < epsilon, "b={}, c={}, b-c={}", b, c, b-c); + }; + + test("1.23456789"); + test("4000.2340293842"); + test("0.0"); + test("1.0"); + test("3.14159274101257324219"); + test("-1.23456789"); + test("-4000.2340293842"); + test("-1.0"); + test("-3.14159274101257324219"); + assert!(d128::from(f64::NAN).is_nan()); + assert!(d128::from(f64::INFINITY).is_infinite()); + assert!(d128::from(f64::NEG_INFINITY).is_negative()); + assert!(d128::from(f64::NEG_INFINITY).is_infinite()); + assert_eq!(d128::from(f64::NEG_INFINITY).as_bytes(), d128::neg_infinity().as_bytes()); + } + + #[bench] + fn bench_from_f64_via_string_conversion(b: &mut Bencher) { + let x = 1.23456789f64; + b.iter(|| d128::from(x)); + } + + #[bench] + fn bench_d128_to_f64_small(b: &mut Bencher) { + let x = d128!(1.23456789); + b.iter(|| f64::from(x)); + } + + #[bench] + fn bench_d128_to_f64_larger(b: &mut Bencher) { + let x = d128!(4000.2340293842); + b.iter(|| f64::from(x)); + } + + #[bench] + fn bench_d128_to_f32_small(b: &mut Bencher) { + let x = d128!(1.23456789); + b.iter(|| f32::from(x)); + } + + #[bench] + fn bench_d128_to_f32_larger(b: &mut Bencher) { + let x = d128!(4000.2340293842); + b.iter(|| f32::from(x)); + } + + #[bench] + fn truncates_long_dec_to_int(b: &mut Bencher) { + let d = d128!(51.55933794806056096535214001488143); + const ONE: d128 = d128::from_raw_bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 34]); + assert_eq!(ONE, d128!(1)); + assert_eq!(d.truncate(ONE), d128!(51)); + b.iter(|| d.truncate(ONE)); + } + + #[test] + fn checks_a_u64_conversion_that_failed_somehow() { + let e: d128 = d128!(100000000); + let x = d128!(10135); + assert_eq!(u64::from(e * x), 1013500000000); + assert_eq!(u64::from(x * e), 1013500000000); + } + + #[test] + fn u128_from_d128_when_lots_of_digits() { + let d = d128!(51.55933794806056096535214001488143); + println!("d = {}, d.digits() = {}", d, d.digits()); + //assert_eq!(u128::from(d), 51u128); + let q = d128!(1e-31); + let r = d.round(q); + println!("d.round(1e-32).digits() = {} (q={}, r={}, d={})", r.digits(), q, r, d); + assert_eq!(u128::from(r), 51u128); + let d = d128!(511.55933794806056096535214001488143); + assert_eq!(u128::from(d.round(q)), 511u128); + + // more than 21 digits, none fractional + // 123456789012345678901234567 + let d = d128!(511559337948060560965352140); + assert_eq!(d.digits(), 27); + assert_eq!(u128::from(d), 511559337948060560965352140u128); + assert!(u128::from(d) > ::std::u64::MAX as u128, "u64 max = {}", ::std::u64::MAX); + // this mimics a real-world scenario I have where I'm scaling a d128 by 1e8 + // to store in an integer, and storing resulting sum in a u64. An intermediate + // result is stored in a u128 as it is effectively (x * y) * 1e16, so that + // is rescaled down by 1e-8 and stored in the u64 sum. u64::MAX * 1e8 is + // therefore the largest value I could expect to encounter in this situation. + // (I think.) + // + let extra_huge = (::std::u64::MAX as u128) * 100_000_000u128; + let d = d128!(1844674407370955161500000001); + assert_eq!(u128::from(d), 1844674407370955161500000001u128); + assert!(u128::from(d) > extra_huge, "u64 max = {}, extra_huge = {}, d = {}", + ::std::u64::MAX, extra_huge, d); + } + + #[test] + fn checks_a_u64_conversion_that_failed_somehow_but_with_u128() { + let e: d128 = d128!(100000000); + let x = d128!(10135); + assert_eq!(u128::from(e * x), 1013500000000); + assert_eq!(u128::from(x * e), 1013500000000); + } + + #[test] + fn checks_a_u64_potential_edge_case() { + let e: d128 = d128!(100000000); + //let e = d128!(1e8); + //assert_eq!(e, E); + let x = d128!(0.12345678); + assert_eq!(u64::from(e * x), 12345678u64); + //assert_eq!(u64::from(E * x), 12345678u64); + assert_eq!(u64::from(x * e), 12345678u64); + } + + #[test] + fn verifies_u64_from_d128_on_large_number_of_examples() { + macro_rules! check { + ($n:expr) => { + let u: u64 = $n; + assert_eq!(u64::from(d128!($n)), u); + } + } + + check!(0); + check!(1); + check!(2); + check!(3); + check!(4); + check!(10); + check!(100); + check!(1456789); + check!(12345678); + check!(123456789); + check!(17473551615); + check!(1744073551615); + check!(1744073709551615); + check!(18446744073709551615); + } + + #[test] + fn verifies_u128_from_d128_on_large_number_of_examples() { + macro_rules! check { + ($n:expr) => { + let u: u128 = $n; + assert_eq!(u128::from(d128!($n)), u); + } + } + + check!(0); + check!(1); + check!(2); + check!(3); + check!(4); + check!(10); + check!(100); + check!(1456789); + check!(12345678); + check!(123456789); + check!(17473551615); + check!(1744073551615); + check!(1744073709551615); + check!(18446744073709551615); + } + + #[bench] + fn sums_vec_of_100_000_u64_1e8_of_float(b: &mut Bencher) { + let x = 0.00012345f64; + let mut xs: Vec = Vec::with_capacity(100_000); + let mut ys: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u64 { + xs.push(((i as f64 * x) * 1e8) as u64); + ys.push(i as f64 * x); + } + + assert!( + xs.iter().sum::() as f64 / 1e8 + - + ys.iter().sum::() + < + 1e-8 + ); + + b.iter(|| { + xs.iter().sum::() as f64 / 1e8 + }); + } + + + #[cfg(feature = "faster")] + #[bench] + fn sums_vec_of_100_000_u64_1e8_of_float_simd(b: &mut Bencher) { + use faster::*; + let x = 0.00012345f64; + let mut xs: Vec = Vec::with_capacity(100_000); + let mut ys: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u64 { + xs.push(((i as f64 * x) * 1e8) as u64); + ys.push(i as f64 * x); + } + + assert!( + ((&xs[..]).simd_iter(u64s(0)) + .simd_reduce(u64s(0), |acc, v| acc + v) + .sum() as f64 / 1e8) + - + ys.iter().sum::() + < + 1e-8 + ); + + b.iter(|| { + ((&xs[..]).simd_iter(u64s(0)) + .simd_reduce(u64s(0), |acc, v| acc + v) + .sum() as f64 / 1e8) + }); + } + + #[cfg(feature = "faster")] + #[bench] + fn sums_vec_of_100_000_f32_simd(b: &mut Bencher) { + use faster::*; + let x = 0.00012345f32; + let mut xs: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u32 { + xs.push(i as f32 * x); + } + + b.iter(|| { + (&xs[..]).simd_iter(f32s(0.0)) + .simd_reduce(f32s(0.0), |acc, v| acc + v) + .sum() + }); + } + + #[bench] + fn sums_vec_of_100_000_f32(b: &mut Bencher) { + let x = 0.00012345f32; + let mut xs: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u32 { + xs.push(i as f32 * x); + } + + b.iter(|| { + xs.iter().sum::() + }); + } + + #[bench] + fn sums_vec_of_100_000_f64(b: &mut Bencher) { + let x = 0.00012345f64; + let mut xs: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u32 { + xs.push(i as f64 * x); + } + + b.iter(|| { + xs.iter().sum::() + }); + } + + #[bench] + fn sums_vec_of_100_000_d128(b: &mut Bencher) { + let x = d128!(0.00012345); + let mut xs: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u32 { + xs.push(d128::from(i) * x); + } + + b.iter(|| { + xs.iter().sum::() + }); + } + + #[bench] + fn d128_mult_1e8_convert_u64_as_i64_mult_neg_one(b: &mut Bencher) { + let price = d128!(6128.1234567); + let to = |x: d128| -> i64 { -(u64::from(x * d128!(1e8)) as i64) }; + let from = |x: i64| -> d128 { d128::from(-x as u64) / d128!(1e8) }; + assert_eq!(price, from(to(price))); + b.iter(|| { + -(u64::from(price * d128!(1e8)) as i64) + }); + } + + #[bench] + fn d128_to_u64(b: &mut Bencher) { + let x = d128!(12345); + b.iter(|| u64::from(x)); + } + + #[bench] + fn d128_to_u128(b: &mut Bencher) { + let x = d128!(12345); + b.iter(|| u128::from(x)); + } + + #[bench] + fn d128_to_u128_max_digits(b: &mut Bencher) { + let x = d128!(51.55933794806056096535214001488143); + b.iter(|| u128::from(x)); + } + + #[bench] + fn d128_to_u128_max_digits_preemptive_truncate(b: &mut Bencher) { + let x = d128!(51.55933794806056096535214001488143); + const ONE: d128 = d128::from_raw_bytes([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 34]); + b.iter(|| u128::from(x.truncate(ONE))); + } + + #[bench] + fn u64_to_d128(b: &mut Bencher) { + let x = 12345u64; + b.iter(|| d128::from(x)); + } + + #[bench] + fn d128_from_f32_via_u64_1e8(b: &mut Bencher) { + let x = 1.2345f32; + b.iter(|| { + d128::from((x * 1e8) as i64) * d128!(1e-8) + }); + } + #[bench] + fn d128_from_f32_via_u64_2_powi_27(b: &mut Bencher) { + let x = 1.2345f32; + b.iter(|| { + // note: 1 / 134_217_728 = 7.450580596923828125E-9 + d128::from((x * 134_217_728f32) as i64) * d128!(7.450580596923828125E-9) + }); + } + + #[bench] + fn d128_from_f32_via_from_str(b: &mut Bencher) { + let x = 1.2345f32; + b.iter(|| { + d128::from_str(&format!("{}", x)) + }); + } + + #[test] + fn validate_from_f64_lossy_small() { + let min = -1_000.0f64; + let max = 1_000.0f64; + let n = (max - min) as usize * 100; + let mut i = min; + for j in 0..n { + i += 0.01; + //let a = d128::from((i * 1e8) as i64) * d128!(1e-8); + let a = d128::from_f64_lossy(i); + let b = d128::from_str(&format!("{}", i)).unwrap(); + assert!((a - b).abs() < d128!(0.0001), "i = {}, a = {}, b = {}, j = {}", i, a, b, j); + } + } + + #[test] + fn validate_from_f64_lossy() { + let min = -500_000.0f64; + let max = 500_000.0f64; + let n = (max - min) as usize * 2; + let mut i = min; + for j in 0..n { + i += 0.5; + //let a = d128::from((i * 1e8) as i64) * d128!(1e-8); + let a = d128::from_f64_lossy(i); + let b = d128::from_str(&format!("{}", i)).unwrap(); + assert!((a - b).abs() < d128!(0.1), "i = {}, a = {}, b = {}, j = {}", i, a, b, j); + } + } + + #[test] + fn validate_from_f32_lossy_small() { + let min = -1_000.0f32; + let max = 1_000.0f32; + let n = (max - min) as usize * 100; + let mut i = min; + for j in 0..n { + i += 0.01; + //let a = d128::from((i * 1e8) as i64) * d128!(1e-8); + let a = d128::from_f32_lossy(i); + let b = d128::from_str(&format!("{}", i)).unwrap(); + assert!((a - b).abs() < d128!(0.0001), "i = {}, a = {}, b = {}, j = {}", i, a, b, j); + } + } + + #[test] + fn validate_from_f32_lossy() { + let min = -500_000.0f32; + let max = 500_000.0f32; + let n = (max - min) as usize * 2; + let mut i = min; + for j in 0..n { + i += 0.5; + //let a = d128::from((i * 1e8) as i64) * d128!(1e-8); + let a = d128::from_f32_lossy(i); + let b = d128::from_str(&format!("{}", i)).unwrap(); + assert!((a - b).abs() < d128!(0.1), "i = {}, a = {}, b = {}, j = {}", i, a, b, j); + } + } + + #[test] + fn it_parses_zero_in_exp_notation() { + assert_eq!(d128::from_str("0E-8").unwrap(), d128!(0.00000000)); + } + + #[test] + fn it_verifies_infinity_fns() { + assert!(d128::infinity().is_infinite()); + assert!(!d128::infinity().is_negative()); + assert!(d128::neg_infinity().is_infinite()); + assert!(d128::neg_infinity().is_negative()); + assert_eq!(d128::infinity() + d128!(1), d128::infinity()); + } + + #[test] + fn test_sum_impl() { + let decimals = vec![d128!(1), d128!(2), d128!(3), d128!(4)]; + assert_eq!(d128!(10), decimals.iter().sum()); + assert_eq!(d128!(10), decimals.into_iter().sum()); + } + + #[test] + fn it_checks_default_is_zero() { + assert_eq!(d128::default(), d128::zero()); + } + + #[test] + fn it_handles_a_real_world_small_number_that_landed_in_db_as_nan() { + let amt = d128!(1E-8); + let price = d128!(0.00143500); + let fee = d128!(1E-8); + let total = d128!(0E-8); + assert_eq!(d128::zero(), total); + let as_calculated = (d128!(1) - fee / total).quantize(d128!(0.00000001)); + assert!(as_calculated.is_nan()); + let fixed = (d128!(1) - fee / total.max(d128!(0.00000001))).quantize(d128!(0.00000001)); + assert!(fixed.is_finite()); + } + + #[test] + fn it_checks_the_max_of_nan_and_a_real_number_is_the_real_number() { + let x = d128!(NaN); + assert!(x.is_nan()); + assert_eq!(x.max(d128::zero()), d128::zero()); + assert_eq!(x.max(d128!(-100)), d128!(-100)); + } + + #[bench] + fn random_number_via_u32_range(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(980_000_000u32, 1_200_000_000u32); + let e = d128!(1_000_000_000); + b.iter(|| { + let d: d128 = d128::from(range.ind_sample(&mut rng)) / e; + d + }); + } + + #[test] + fn it_validates_range_of_random_number_via_u32_range() { + let mut rng = rand::thread_rng(); + let range = Range::new(980_000_000u32, 1_200_000_000u32); + let e = d128!(1_000_000_000); + let d: d128 = d128::from(range.ind_sample(&mut rng)) / e; + println!("d={}", d); + assert!(d >= d128!(0.98)); + assert!(d <= d128!(1.2)); + } + + #[bench] + fn random_number_via_u32(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d128 = rng.gen::().into(); + d + }); + } + + #[bench] + fn d128_via_random_u64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d128 = rng.gen::().into(); + d + }); + } + + #[bench] + fn d128_via_random_i64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d128 = rng.gen::().into(); + d + }); + } + + #[bench] + fn rand_0_1_via_f64_baseline(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(-1f64, 1f64); + b.iter(|| { + d128::baseline_from_f64_lossy(range.ind_sample(&mut rng)) + }); + } + + #[bench] + fn rand_0_1_via_f64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(-1f64, 1f64); + b.iter(|| { + d128::from_f64_lossy(range.ind_sample(&mut rng)) + }); + } + + #[bench] + fn rand_0_1_via_f32(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(-1f32, 1f32); + b.iter(|| { + d128::from_f32_lossy(range.ind_sample(&mut rng)) + }); + } + + #[bench] + fn rand_0_1_via_u32(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(0u32, 2_000_000u32); + let e = d128!(1_000_000); + b.iter(|| { + (d128::from(range.ind_sample(&mut rng)) - e) / e + }); + } + + #[bench] + fn rand_0_1_via_i32(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(-1_000_000i32, 1_000_000i32); + let e = d128!(1_000_000); + b.iter(|| { + d128::from(range.ind_sample(&mut rng)) / e + }); + } + + #[test] + fn test_deref_does_not_blow_the_machine_up() { + fn add(a: &d128, b: &d128) -> d128 { + *a + *b + } + let a = d128!(1); + let b = d128!(1); + let c = add(&a, &b); + assert_eq!(c, d128!(2)); + } + + #[test] + fn test_deref_mutate() { + let a = &mut d128!(1.011); + *a += d128!(1.022); + assert_eq!(a, &d128!(2.033)); + } + #[test] fn default() { assert_eq!(d128::zero(), d128::default()); diff --git a/src/dec64.rs b/src/dec64.rs new file mode 100644 index 00000000..e538691f --- /dev/null +++ b/src/dec64.rs @@ -0,0 +1,1461 @@ +use super::Class; +use super::Status; +use super::Rounding; + +use crate::context::*; +use libc::{c_char, int32_t, uint8_t, uint32_t}; +#[cfg(feature = "ord_subset")] +use ord_subset; +#[cfg(feature = "rustc-serialize")] +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +#[cfg(feature = "serde")] +use serde; +#[cfg(feature = "slog")] +use slog; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::default::Default; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::iter::Sum; +use std::mem::MaybeUninit; +use std::num::FpCategory; +use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Rem, RemAssign, + Neg, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, + ShlAssign, Shr, ShrAssign, Deref}; +use std::str::FromStr; +use std::str::from_utf8_unchecked; +use crate::d128; + +thread_local!(static CTX: RefCell = RefCell::new(d64::default_context())); +thread_local!(static ROUND_DOWN: RefCell = RefCell::new(d64::with_rounding(Rounding::Down))); +thread_local!(static HALF_UP: RefCell = RefCell::new(d64::with_rounding(Rounding::HalfUp))); + + +#[repr(C)] +#[derive(Clone, Copy)] +/// A 64-bit decimal floating point type. +pub struct d64 { + bytes: [uint8_t; 8], +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct decNumber { + digits: i32, + exponent: i32, + bits: u8, + // DECPUN = 3 because this is the fastest for conversion between decNumber and decDouble + // DECNUMDIGITS = 34 because we use decDouble only + // 12 = ((DECNUMDIGITS+DECDPUN-1)/DECDPUN) + lsu: [u16; 12], +} + +impl Default for d64 { + fn default() -> Self { + d64::zero() + } +} + +#[cfg(feature = "ord_subset")] +impl ord_subset::OrdSubset for d64 { + fn is_outside_order(&self) -> bool { + self.is_nan() + } +} + +#[cfg(feature = "ord_subset")] +impl Into> for d64 { + fn into(self) -> ord_subset::OrdVar { + ord_subset::OrdVar::new(self) + } +} + +#[cfg(feature = "rustc-serialize")] +impl Decodable for d64 { + fn decode(d: &mut D) -> Result { + let s = r#try!(d.read_str()); + Ok(Self::from_str(&s).expect("unreachable")) + } +} + +#[cfg(feature = "rustc-serialize")] +impl Encodable for d64 { + fn encode(&self, e: &mut E) -> Result<(), E::Error> { + e.emit_str(&format!("{}", self)) + } +} + +impl Hash for d64 { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +#[cfg(feature = "serde")] +impl serde::ser::Serialize for d64 { + fn serialize(&self, serializer: S) -> Result + where S: serde::ser::Serializer + { + serializer.serialize_str(&self.to_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::de::Deserialize<'de> for d64 { + fn deserialize(deserializer: D) -> Result + where D: serde::de::Deserializer<'de> + { + deserializer.deserialize_str(d64Visitor) + } +} + +#[cfg(feature = "serde")] +#[allow(non_camel_case_types)] +struct d64Visitor; + +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for d64Visitor { + type Value = d64; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a d64 value") + } + + fn visit_str(self, s: &str) -> Result + where E: serde::de::Error + { + use serde::de::Unexpected; + d64::from_str(s).map_err(|_| E::invalid_value(Unexpected::Str(s), &self)) + } +} + +/// Converts an i32 to d64. The result is exact and no error is possible. +impl From for d64 { + fn from(val: i32) -> d64 { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromInt32(res.as_mut_ptr(), val) + } + } +} + +/// Converts an u32 to d64. The result is exact and no error is possible. +impl From for d64 { + fn from(val: u32) -> d64 { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromUInt32(res.as_mut_ptr(), val) + } + } +} + +/// Converts an u64 to d64. The result is exact and no error is possible. +impl From for d64 { + fn from(val: u64) -> d64 { + /* + let mut bcd = [0u8; 16]; + let mut i = 15; + while val > 0 { + bcd[i] = (val % 10) as u8; + val /= 10; + i -= 1; + } + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromBCD(res.as_mut_ptr(), 0, bcd.as_ptr(), 0) + } + */ + let wider = d128::from(val); + d64::from(wider) + } +} + +/// Converts an i64 to d64. The result is exact and no error is possible. +impl From for d64 { + fn from(val: i64) -> d64 { + if val < 0 { + -d64::from(!(val as u64) + 1) + } else { + d64::from(val as u64) + } + } +} + +impl AsRef for d64 { + fn as_ref(&self) -> &d64 { + &self + } +} + +impl Deref for d64 { + type Target = [uint8_t; 8]; + + fn deref(&self) -> &[uint8_t; 8] { + &self.bytes + } +} + +/// Converts a string to d64. The length of the coefficient and the size of the exponent are +/// checked by this routine, so rounding will be applied if necessary, and this may set status +/// flags (`UNDERFLOW`, `OVERFLOW`) will be reported, or rounding applied, as necessary. There is +/// no limit to the coefficient length for finite inputs; NaN payloads must be integers with no +/// more than 33 digits. Exponents may have up to nine significant digits. The syntax of the string +/// is fully checked; if it is not valid, the result will be a quiet NaN and an error flag will be +/// set. +impl FromStr for d64 { + type Err = (); + fn from_str(s: &str) -> Result { + let cstr = match CString::new(s) { + Err(..) => CString::new("qNaN").unwrap(), + Ok(cstr) => cstr, + }; + d64::with_context(|ctx| { + let res: d64 = unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + decDoubleFromString(res.as_mut_ptr(), cstr.as_ptr(), ctx); + res.assume_init() + }; + Ok(res) + }) + } +} + +/// Converts this d64 to an i32. It uses Rounding::HalfEven. +impl Into for d64 { + fn into(self) -> i32 { + d64::with_context(|ctx| unsafe { decDoubleToInt32(&self, ctx, ctx.rounding) }) + } +} + +/// Converts this d64 to an u32. It uses Rounding::HalfEven. +impl Into for d64 { + fn into(self) -> u32 { + d64::with_context(|ctx| unsafe { decDoubleToUInt32(&self, ctx, ctx.rounding) }) + } +} + +/// Formats a d64. Finite numbers will be converted to a string with exponential notation if the +/// exponent is positive or if the magnitude of x is less than 1 and would require more than five +/// zeros between the decimal point and the first significant digit. Note that strings which are +/// not simply numbers (one of Infinity, –Infinity, NaN, or sNaN) are possible. A NaN string may +/// have a leading – sign and/or following payload digits. No digits follow the NaN string if the +/// payload is 0. +impl fmt::Display for d64 { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut buf = [0; 43]; + unsafe { + decDoubleToString(self, buf.as_mut().as_mut_ptr()); + let cstr = CStr::from_ptr(buf.as_ptr()); + fmt.pad(from_utf8_unchecked(cstr.to_bytes())) + } + } +} + +/// Same as `fmt::Display`. +impl fmt::Debug for d64 { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +/// Formats a d64 with engineering notation. This is the same as fmt::Display except that if +/// exponential notation is used the exponent will be a multiple of 3. +impl fmt::LowerExp for d64 { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut buf = [0; 43]; + unsafe { + decDoubleToEngString(self, buf.as_mut().as_mut_ptr()); + let cstr = CStr::from_ptr(buf.as_ptr()); + fmt.pad(from_utf8_unchecked(cstr.to_bytes())) + } + } +} + +/// Formats a d64 to hexadecimal binary representation. +impl fmt::LowerHex for d64 { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + for b in self.bytes.iter().rev() { + r#try!(write!(fmt, "{:02x}", b)); + } + Ok(()) + } +} + +impl PartialEq for d64 { + fn eq(&self, other: &d64) -> bool { + self.compare(other).is_zero() + } +} + +impl PartialOrd for d64 { + fn partial_cmp(&self, other: &d64) -> Option<::std::cmp::Ordering> { + use std::cmp::Ordering; + match self.compare(other) { + v if v.is_nan() => None, + v if v.is_zero() => Some(Ordering::Equal), + v if v.is_positive() => Some(Ordering::Greater), + v if v.is_negative() => Some(Ordering::Less), + _ => unreachable!(), + } + } +} + +macro_rules! ffi_unary_op { + ($(#[$attr:meta])* impl $op:ident, $method:ident, $ffi:ident for $t:ident) => { + $(#[$attr])* + impl $op for $t { + type Output = $t; + + fn $method(mut self) -> $t { + $t::with_context(|ctx| { + unsafe { *$ffi(&mut self, &self, ctx)} + }) + } + } + + impl<'a> $op for &'a $t { + type Output = $t; + + fn $method(self) -> $t { + $t::with_context(|ctx| { + unsafe { + let mut res: MaybeUninit<$t> = MaybeUninit::uninit(); + *$ffi(res.as_mut_ptr(), self, ctx) + } + }) + } + } + } +} + +macro_rules! ffi_binary_op { + ($(#[$attr:meta])* impl $op:ident, $method:ident, $ffi:ident for $t:ident) => { + $(#[$attr])* + impl $op<$t> for $t { + type Output = $t; + + fn $method(mut self, other: $t) -> $t { + $t::with_context(|ctx| { + unsafe { *$ffi(&mut self, &self, &other, ctx)} + }) + } + } + + impl<'a> $op<$t> for &'a $t { + type Output = $t; + + fn $method(self, mut other: $t) -> $t { + $t::with_context(|ctx| { + unsafe { *$ffi(&mut other, self, &other, ctx) } + }) + } + } + + impl<'a> $op<&'a$t> for $t { + type Output = $t; + + fn $method(mut self, other: &'a $t) -> $t { + $t::with_context(|ctx| { + unsafe { *$ffi(&mut self, &self, other, ctx) } + }) + } + } + + impl<'a, 'b> $op<&'a $t> for &'b $t { + type Output = $t; + + fn $method(self, other: &'a $t) -> $t { + $t::with_context(|ctx| { + unsafe { + let mut res: MaybeUninit<$t> = MaybeUninit::uninit(); + *$ffi(res.as_mut_ptr(), self, other, ctx) } + }) + } + } + } +} + +macro_rules! ffi_unary_assign_op { + ($(#[$attr:meta])* impl $op:ident, $method:ident, $ffi:ident for $t:ident) => { + $(#[$attr])* + impl $op<$t> for $t { + fn $method(&mut self, other: $t) { + $t::with_context(|ctx| { + unsafe { $ffi(self, self, &other, ctx); } + }) + } + } + } +} + +ffi_binary_op!(impl Add, add, decDoubleAdd for d64); +ffi_binary_op!(impl Sub, sub, decDoubleSubtract for d64); +ffi_binary_op!(impl Mul, mul, decDoubleMultiply for d64); +ffi_binary_op!(impl Div, div, decDoubleDivide for d64); +ffi_binary_op!( +/// The operands must be zero or positive, an integer (finite with zero exponent) and comprise +/// only zeros and/or ones; if not, INVALID_OPERATION is set. + impl BitAnd, bitand, decDoubleAnd for d64); +ffi_binary_op!( +/// The operands must be zero or positive, an integer (finite with zero exponent) and comprise +/// only zeros and/or ones; if not, INVALID_OPERATION is set. + impl BitOr, bitor, decDoubleOr for d64); +ffi_binary_op!( +/// The operands must be zero or positive, an integer (finite with zero exponent) and comprise +/// only zeros and/or ones; if not, INVALID_OPERATION is set. + impl BitXor, bitxor, decDoubleXor for d64); +ffi_binary_op!(impl Rem, rem, decDoubleRemainder for d64); + +ffi_unary_assign_op!(impl AddAssign, add_assign, decDoubleAdd for d64); +ffi_unary_assign_op!(impl SubAssign, sub_assign, decDoubleSubtract for d64); +ffi_unary_assign_op!(impl MulAssign, mul_assign, decDoubleMultiply for d64); +ffi_unary_assign_op!(impl DivAssign, div_assign, decDoubleDivide for d64); +ffi_unary_assign_op!(impl BitAndAssign, bitand_assign, decDoubleAnd for d64); +ffi_unary_assign_op!(impl BitOrAssign, bitor_assign, decDoubleOr for d64); +ffi_unary_assign_op!(impl BitXorAssign, bitxor_assign, decDoubleXor for d64); +ffi_unary_assign_op!(impl RemAssign, rem_assign, decDoubleRemainder for d64); + +ffi_unary_op!(impl Neg, neg, decDoubleMinus for d64); +ffi_unary_op!( +/// The operand must be zero or positive, an integer (finite with zero exponent) and comprise +/// only zeros and/or ones; if not, INVALID_OPERATION is set. + impl Not, not, decDoubleInvert for d64); + +/// The result is `self` with the digits of the coefficient shifted to the left without adjusting +/// the exponent or the sign of `self`. Any digits ‘shifted in’ from the right will be 0. `amount` +/// is the count of positions to shift and must be a in the range –34 through +34. NaNs are +/// propagated as usual. If `self` is infinite the result is Infinity of the same sign. No status +/// is set unless `amount` is invalid or `self` is an sNaN. +impl Shl for d64 { + type Output = d64; + + fn shl(mut self, amount: usize) -> d64 { + let shift = d64::from(amount as u32); + d64::with_context(|ctx| unsafe { *decDoubleShift(&mut self, &self, &shift, ctx) }) + } +} + +impl<'a> Shl for &'a d64 { + type Output = d64; + + fn shl(self, amount: usize) -> d64 { + let shift = d64::from(amount as u32); + d64::with_context(|ctx| { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleShift(res.as_mut_ptr(), self, &shift, ctx) + } + }) + } +} + +impl ShlAssign for d64 { + fn shl_assign(&mut self, amount: usize) { + let shift = d64::from(amount as u32); + d64::with_context(|ctx| { + unsafe { + decDoubleShift(self, self, &shift, ctx); + } + }) + } +} + +/// The result is `self` with the digits of the coefficient shifted to the right without adjusting +/// the exponent or the sign of `self`. Any digits ‘shifted in’ from the left will be 0. `amount` +/// is the count of positions to shift and must be a in the range –34 through +34. NaNs are +/// propagated as usual. If `self` is infinite the result is Infinity of the same sign. No status +/// is set unless `amount` is invalid or `self` is an sNaN. +impl Shr for d64 { + type Output = d64; + + fn shr(mut self, amount: usize) -> d64 { + let shift = -d64::from(amount as u32); + d64::with_context(|ctx| unsafe { *decDoubleShift(&mut self, &self, &shift, ctx) }) + } +} + +impl<'a> Shr for &'a d64 { + type Output = d64; + + fn shr(self, amount: usize) -> d64 { + let shift = -d64::from(amount as u32); + d64::with_context(|ctx| { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleShift(res.as_mut_ptr(), self, &shift, ctx) + } + }) + } +} + +impl ShrAssign for d64 { + fn shr_assign(&mut self, amount: usize) { + let shift = -d64::from(amount as u32); + d64::with_context(|ctx| { + unsafe { + decDoubleShift(self, self, &shift, ctx); + } + }) + } +} + +impl Sum for d64 where T: Borrow { + fn sum>(iter: I) -> d64 { + iter.into_iter() + .fold(d64::zero(), |acc, val| + acc + val.borrow()) + } +} + +impl d64 { + fn default_context() -> Context { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decContextDefault(res.as_mut_ptr(), 64) + } + } + + /// Initialize a `Context` with the specified `Rounding`. + fn with_rounding(rounding: Rounding) -> Context { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + let mut ctx = *decContextDefault(res.as_mut_ptr(), 64); + decContextSetRounding(&mut ctx, rounding as u32); + ctx + } + } + + fn with_round_down(f: F) -> R + where F: FnOnce(&mut Context) -> R + { + ROUND_DOWN.with(|ctx| f(&mut ctx.borrow_mut())) + } + + fn with_half_up(f: F) -> R + where F: FnOnce(&mut Context) -> R + { + HALF_UP.with(|ctx| f(&mut ctx.borrow_mut())) + } + + fn with_context(f: F) -> R + where F: FnOnce(&mut Context) -> R + { + CTX.with(|ctx| f(&mut ctx.borrow_mut())) + } + + /// Creates a d64 from raw bytes. Endianess is host dependent. + pub const fn from_raw_bytes(bytes: [u8; 8]) -> d64 { + d64 { bytes } + } + + /// Returns raw bytes for this d64. Endianess is host dependent. + pub fn to_raw_bytes(&self) -> [u8; 8] { + self.bytes + } + + pub fn as_bytes(&self) -> [u8; 8] { + self.bytes + } + + /// Returns the thread local status. + pub fn get_status() -> Status { + d64::with_context(|ctx| Status::from_bits_truncate(ctx.status)) + } + + /// Sets the thread local status. + pub fn set_status(status: Status) { + d64::with_context(|ctx| ctx.status = status.bits()); + } + + /// Reads the hex binary representation from a string. This is the reverse of formatting with + /// {:x}. + pub fn from_hex(s: &str) -> d64 { + if s.len() != 16 { + Self::from_str("qNaN").unwrap() + } else { + unsafe { + let mut res: d64 = MaybeUninit::zeroed().assume_init(); + for (i, octet) in s.as_bytes().chunks(2).rev().enumerate() { + //println!("i = {}, octet = {:?}", i, octet); + res.bytes[i] = match u8::from_str_radix(from_utf8_unchecked(octet), 16) { + Ok(val) => val, + Err(..) => return Self::from_str("qNaN").unwrap(), + }; + } + res + } + } + } + + // Utilities and conversions, extractors, etc. + + /// Returns the d64 representing +0. + pub fn zero() -> d64 { + unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleZero(res.as_mut_ptr()) + } + } + + /// Returns the d64 representing +Infinity. + pub fn infinity() -> d64 { + d64::from_str("Infinity").unwrap() + } + + /// Returns the d64 representing -Infinity. + pub fn neg_infinity() -> d64 { + d64::from_str("-Infinity").unwrap() + } + + // Computational. + + /// Returns the absolute value of `self`. + pub fn abs(mut self) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleAbs(&mut self, &self, ctx) }) + } + + /// Calculates the fused multiply-add `self` × `a` + `b` and returns the result. The multiply + /// is carried out first and is exact, so this operation has only the one, final, rounding. + pub fn mul_add>(mut self, a: O, b: O) -> d64 { + d64::with_context(|ctx| unsafe { + *decDoubleFMA(&mut self, &self, a.as_ref(), b.as_ref(), ctx) + }) + } + + /// Returns the adjusted exponent of `self`, according to IEEE 754 rules. That is, the exponent + /// returned is calculated as if the decimal point followed the first significant digit (so, + /// for example, if `self` were 123 then the result would be 2). If `self` is infinite, the + /// result is +Infinity. If `self` is a zero, the result is –Infinity, and the + /// `DIVISION_BY_ZERO` flag is set. If `self` is less than zero, the absolute value of `self` + /// is used. If `self` is 1, the result is 0. NaNs are handled (propagated) as for arithmetic + /// operations. + pub fn logb(mut self) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleLogB(&mut self, &self, ctx) }) + } + + /// If both `self` and `other` are numeric (not NaNs) this returns the larger of the two + /// (compared using total ordering, to give a well-defined result). If either (but not both of) + /// is a quiet NaN then the other argument is the result; otherwise NaNs are handled as for + /// arithmetic operations. + pub fn max>(mut self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleMax(&mut self, &self, other.as_ref(), ctx) }) + } + + /// If both `self` and `other` are numeric (not NaNs) this returns the smaller of the two + /// (compared using total ordering, to give a well-defined result). If either (but not both of) + /// is a quiet NaN then the other argument is the result; otherwise NaNs are handled as for + /// arithmetic operations. + pub fn min>(mut self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleMin(&mut self, &self, other.as_ref(), ctx) }) + } + + /// Returns the ‘next’ d64 to `self` in the direction of +Infinity according to IEEE 754 rules + /// for nextUp. The only status possible is `INVALID_OPERATION` (from an sNaN). + pub fn next(mut self) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleNextPlus(&mut self, &self, ctx) }) + } + + /// Returns the ‘next’ d64 to `self` in the direction of –Infinity according to IEEE 754 rules + /// for nextDown. The only status possible is `INVALID_OPERATION` (from an sNaN). + pub fn previous(mut self) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleNextMinus(&mut self, &self, ctx) }) + } + + /// The number is set to the result of raising `self` to the power of `exp`. Results will be + /// exact when `exp` has an integral value and the result does not need to be rounded, and also + /// will be exact in certain special cases, such as when `self` is a zero (see the arithmetic + /// specification for details). Inexact results will always be full precision, and will almost + /// always be correctly rounded, but may be up to 1 _ulp_ (unit in last place) in error in rare + /// cases. This is a mathematical function; the 106 restrictions on precision and + /// range apply as described above, except that the normal range of values is allowed if `exp` + /// has an integral value in the range –1999999997 through +999999999. + pub fn pow>(mut self, exp: O) -> d64 { + d64::with_context(|ctx| unsafe { + let mut num_self: decNumber = MaybeUninit::uninit().assume_init(); + let mut num_exp: decNumber = MaybeUninit::uninit().assume_init(); + decimal64ToNumber(&self, &mut num_self); + decimal64ToNumber(exp.as_ref(), &mut num_exp); + decNumberPower(&mut num_self, &num_self, &num_exp, ctx); + *decimal64FromNumber(&mut self, &num_self, ctx) + }) + } + + /// The number is set to _e_ raised to the power of `exp`. Finite results will always be full + /// precision and inexact, except when `exp` is a zero or –Infinity (giving 1 or 0 + /// respectively). Inexact results will almost always be correctly rounded, but may be up to 1 + /// ulp (unit in last place) in error in rare cases. This is a mathematical function; the + /// 106 restrictions on precision and range apply as described above. + pub fn exp>(mut self, exp: O) -> d64 { + d64::with_context(|ctx| unsafe { + let mut num_self: decNumber = MaybeUninit::uninit().assume_init(); + let mut num_exp: decNumber = MaybeUninit::uninit().assume_init(); + decimal64ToNumber(&self, &mut num_self); + decimal64ToNumber(exp.as_ref(), &mut num_exp); + decNumberExp(&mut num_self, &num_self, &num_exp, ctx); + *decimal64FromNumber(&mut self, &num_self, ctx) + }) + } + + /// The number is set to the natural logarithm (logarithm in base e) of `self`. `self` must be + /// positive or a zero. Finite results will always be full precision and inexact, except when + /// `self` is equal to 1, which gives an exact result of 0. Inexact results will almost always + /// be correctly rounded, but may be up to 1 ulp (unit in last place) in error in rare cases. + /// This is a mathematical function; the 106 restrictions on precision and range + /// apply as described above. + pub fn ln(mut self) -> d64 { + d64::with_context(|ctx| unsafe { + let mut num_self: decNumber = MaybeUninit::uninit().assume_init(); + decimal64ToNumber(&self, &mut num_self); + decNumberLn(&mut num_self, &num_self, ctx); + *decimal64FromNumber(&mut self, &num_self, ctx) + }) + } + + /// The number is set to the logarithm in base ten of `self`. `self` must be positive or a + /// zero. Finite results will always be full precision and inexact, except when `self` is equal + /// to an integral power of ten, in which case the result is the exact integer. Inexact results + /// will almost always be correctly rounded, but may be up to 1 ulp (unit in last place) in + /// error in rare cases. This is a mathematical function; the 106 restrictions on + /// precision and range apply as described above. + pub fn log10(mut self) -> d64 { + d64::with_context(|ctx| unsafe { + let mut num_self: decNumber = MaybeUninit::uninit().assume_init(); + decimal64ToNumber(&self, &mut num_self); + decNumberLog10(&mut num_self, &num_self, ctx); + *decimal64FromNumber(&mut self, &num_self, ctx) + }) + } + + /// Returns the ‘next’ d64 to `self` in the direction of `other` according to proposed IEEE + /// 754 rules for nextAfter. If `self` == `other` the result is `self`. If either operand is + /// a NaN the result is as for arithmetic operations. Otherwise (the operands are numeric and + /// different) the result of adding (or subtracting) an infinitesimal positive amount to `self` + /// and rounding towards +Infinity (or –Infinity) is returned, depending on whether `other` is + /// larger (or smaller) than `self`. The addition will set flags, except that if the result is + /// normal (finite, non-zero, and not subnormal) no flags are set. + pub fn towards>(mut self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { + *decDoubleNextToward(&mut self, &self, other.as_ref(), ctx) + }) + } + + /// Returns `self` set to have the same quantum as `other`, if possible (that is, numerically + /// the same value but rounded or padded if necessary to have the same exponent as `other`, for + /// example to round a monetary quantity to cents). + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d64!(0.1); + /// assert_eq!(d64!(0.400012342423).quantize(prec), d64!(0.4)); + /// // uses default rounding (half even) + /// assert_eq!(d64!(0.05).quantize(prec), d64!(0.0)); + /// assert_eq!(d64!(0.15).quantize(prec), d64!(0.2)); + /// } + /// ``` + pub fn quantize>(mut self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleQuantize(&mut self, &self, other.as_ref(), ctx) }) + } + + /// Like `quantize`, but uses `Rounding::Down`. + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d64!(0.1); + /// assert_eq!(d64!(0.05).truncate(prec), d64!(0.0)); + /// assert_eq!(d64!(0.15).truncate(prec), d64!(0.1)); + /// assert_eq!(d64!(0.19).truncate(prec), d64!(0.1)); + /// } + /// ``` + pub fn truncate>(mut self, other: O) -> d64 { + d64::with_round_down(|ctx| unsafe { *decDoubleQuantize(&mut self, &self, other.as_ref(), ctx) }) + } + + /// Like `quantize`, but uses `Rounding::HalfUp`. + /// + /// # Examples + /// + /// ``` + /// #![feature(proc_macro_hygiene)] + /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; + /// + /// fn main() { + /// let prec = d64!(0.1); + /// assert_eq!(d64!(0.15).round(prec), d64!(0.2)); + /// assert_eq!(d64!(0.14999999999).round(prec), d64!(0.1)); + /// assert_eq!(d64!(0.19).round(prec), d64!(0.2)); + /// assert_eq!(d64!(0.05).round(prec), d64!(0.1)); + /// } + /// ``` + pub fn round>(mut self, other: O) -> d64 { + d64::with_half_up(|ctx| unsafe { *decDoubleQuantize(&mut self, &self, other.as_ref(), ctx) }) + } + + /// Returns a copy of `self` with its coefficient reduced to its shortest possible form without + /// changing the value of the result. This removes all possible trailing zeros from the + /// coefficient (some may remain when the number is very close to the most positive or most + /// negative number). Infinities and NaNs are unchanged and no status is set unless `self` is + /// an sNaN. If `self` is a zero the result exponent is 0. + pub fn reduce(mut self) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleReduce(&mut self, &self, ctx) }) + } + + /// The result is a copy of `self` with the digits of the coefficient rotated to the left (if + /// `amount` is positive) or to the right (if `amount` is negative) without adjusting the + /// exponent or the sign of `self`. `amount` is the count of positions to rotate and must be a + /// finite integer (with exponent=0) in the range -34 through +34. NaNs are propagated as + /// usual. If `self` is infinite the result is Infinity of the same sign. No status is set + /// unless `amount` is invalid or an operand is an sNaN. + pub fn rotate>(mut self, amount: O) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleRotate(&mut self, &self, amount.as_ref(), ctx) }) + } + + /// This calculates `self` × 10`other` and returns the result. `other` must be an + /// integer (finite with exponent=0) in the range ±2 × (34 + 6144), typically resulting from + /// `logb`. Underflow and overflow might occur. NaNs propagate as usual. + pub fn scaleb>(mut self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { *decDoubleScaleB(&mut self, &self, other.as_ref(), ctx) }) + } + + // Comparisons. + + /// Compares `self` and `other` numerically and returns the result. The result may be –1, 0, 1, + /// or NaN (unordered); –1 indicates that `self` is less than `other`, 0 indicates that they + /// are numerically equal, and 1 indicates that `self` is greater than `other`. NaN is returned + /// only if `self` or `other` is a NaN. + pub fn compare>(&self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleCompare(res.as_mut_ptr(), self, other.as_ref(), ctx) + }) + } + + /// Compares `self` and `other` using the IEEE 754 total ordering (which takes into account the + /// exponent) and returns the result. No status is set (a signaling NaN is ordered between + /// Infinity and NaN). The result will be –1, 0, or 1. + pub fn compare_total>(&self, other: O) -> d64 { + d64::with_context(|ctx| unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleCompareTotal(res.as_mut_ptr(), self, other.as_ref(), ctx) + }) + } + + // Copies. + + /// Returns `self` ensuring that the encoding is canonical. + pub fn canonical(mut self) -> d64 { + unsafe { *decDoubleCanonical(&mut self, &self) } + } + + // Non-computational. + + /// Returns the class of `self`. + pub fn class(&self) -> Class { + unsafe { decDoubleClass(self) } + } + + /// Same as `class()` but returns `std::num::FpCategory`. + pub fn classify(&self) -> FpCategory { + use std::num::FpCategory::*; + use super::Class::*; + + match self.class() { + Qnan | Snan => Nan, + PosInf | NegInf => Infinite, + PosZero | NegZero => Zero, + PosNormal | NegNormal => Normal, + PosSubnormal | NegSubnormal => Subnormal, + } + } + + /// Returns the number of significant digits in `self`. If `self` is a zero or is infinite, 1 + /// is returned. If `self` is a NaN then the number of digits in the payload is returned. + pub fn digits(&self) -> u32 { + unsafe { decDoubleDigits(self) } + } + + /// Returns `true` if the encoding of `self` is canonical, or `false` otherwise. + pub fn is_canonical(&self) -> bool { + unsafe { decDoubleIsCanonical(self) != 0 } + } + + /// Returns `true` if `self` is neither infinite nor a NaN, or `false` otherwise. + pub fn is_finite(&self) -> bool { + unsafe { decDoubleIsFinite(self) != 0 } + } + + /// Returns `true` if `self` is finite and its exponent is zero, or `false` otherwise. + pub fn is_integer(&self) -> bool { + unsafe { decDoubleIsInteger(self) != 0 } + } + + /// Returns `true` if `self` is a valid argument for logical operations (that is, `self` is + /// zero or positive, an integer (finite with a zero exponent) and comprises only zeros and/or + /// ones), or `false` otherwise. + pub fn is_logical(&self) -> bool { + unsafe { decDoubleIsLogical(self) != 0 } + } + + /// Returns `true` if the encoding of `self` is an infinity, or `false` otherwise. + pub fn is_infinite(&self) -> bool { + unsafe { decDoubleIsInfinite(self) != 0 } + } + + /// Returns `true` if `self` is a NaN (quiet or signaling), or `false` otherwise. + pub fn is_nan(&self) -> bool { + unsafe { decDoubleIsNaN(self) != 0 } + } + + /// Returns `true` if `self` is less than zero and not a NaN, or `false` otherwise. + pub fn is_negative(&self) -> bool { + unsafe { decDoubleIsNegative(self) != 0 } + } + + /// Returns `true` if `self` is a normal number (that is, is finite, non-zero, and not + /// subnormal), or `false` otherwise. + pub fn is_normal(&self) -> bool { + unsafe { decDoubleIsNormal(self) != 0 } + } + + /// Returns `true` if `self` is greater than zero and not a NaN, or `false` otherwise. + pub fn is_positive(&self) -> bool { + unsafe { decDoubleIsPositive(self) != 0 } + } + + /// Returns `true` if `self` is a signaling NaN, or `false` otherwise. + pub fn is_signaling(&self) -> bool { + unsafe { decDoubleIsSignaling(self) != 0 } + } + + /// Returns `true` if `self` has a minus sign, or `false` otherwise. Note that zeros and NaNs + /// may have a minus sign. + pub fn is_signed(&self) -> bool { + unsafe { decDoubleIsSigned(self) != 0 } + } + + /// Returns `true` if `self` is subnormal (that is, finite, non-zero, and with magnitude less + /// than 10-6143), or `false` otherwise. + pub fn is_subnormal(&self) -> bool { + unsafe { decDoubleIsSubnormal(self) != 0 } + } + + /// Returns `true` if `self` is zero, or `false` otherwise. + pub fn is_zero(&self) -> bool { + unsafe { decDoubleIsZero(self) != 0 } + } + + pub fn finite_or(self, default: d64) -> d64 { + match self.is_finite() { + true => self, + false => default + } + } + + pub fn finite_or_else d64>(self, f: F) -> d64 { + match self.is_finite() { + true => self, + false => f() + } + } +} + +#[cfg(feature = "slog")] +impl slog::Value for d64 { + fn serialize( + &self, + _record: &slog::Record, + key: slog::Key, + serializer: &mut dyn slog::Serializer, + ) -> Result<(), slog::Error> { + serializer.emit_arguments(key, &format_args!("{}", self)) + } +} + +impl From for d64 { + fn from(val: d128) -> Self { + d64::with_context(|ctx| unsafe { + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromWider(res.as_mut_ptr(), &val, ctx) + }) + } +} + +extern "C" { + // Context. + fn decContextDefault(ctx: *mut Context, kind: uint32_t) -> *mut Context; + fn decContextSetRounding(ctx: *mut Context, rounding: uint32_t); + // Utilities and conversions, extractors, etc. + //fn decDoubleFromBCD(res: *mut d64, exp: i32, bcd: *const u8, sign: i32) -> *mut d64; + fn decDoubleFromInt32(res: *mut d64, src: int32_t) -> *mut d64; + fn decDoubleFromString(res: *mut d64, s: *const c_char, ctx: *mut Context) -> *mut d64; + fn decDoubleFromUInt32(res: *mut d64, src: uint32_t) -> *mut d64; + fn decDoubleToString(src: *const d64, s: *mut c_char) -> *mut c_char; + fn decDoubleToInt32(src: *const d64, ctx: *mut Context, round: Rounding) -> int32_t; + fn decDoubleToUInt32(src: *const d64, ctx: *mut Context, round: Rounding) -> uint32_t; + fn decDoubleFromWider(res: *mut d64, src: *const d128, ctx: *mut Context) -> *mut d64; + fn decDoubleToEngString(res: *const d64, s: *mut c_char) -> *mut c_char; + fn decDoubleZero(res: *mut d64) -> *mut d64; + // Computational. + fn decDoubleAbs(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleAdd(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleAnd(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleDivide(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleFMA(res: *mut d64, + a: *const d64, + b: *const d64, + c: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleInvert(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleLogB(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleMax(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleMin(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleMinus(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleMultiply(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleNextMinus(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleNextPlus(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleNextToward(res: *mut d64, + src: *const d64, + other: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleOr(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleQuantize(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleReduce(res: *mut d64, src: *const d64, ctx: *mut Context) -> *mut d64; + fn decDoubleRemainder(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleRotate(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleScaleB(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleShift(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleSubtract(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleXor(res: *mut d64, a: *const d64, b: *const d64, ctx: *mut Context) -> *mut d64; + // Comparisons. + fn decDoubleCompare(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + fn decDoubleCompareTotal(res: *mut d64, + a: *const d64, + b: *const d64, + ctx: *mut Context) + -> *mut d64; + // Copies. + fn decDoubleCanonical(res: *mut d64, src: *const d64) -> *mut d64; + // Non-computational. + fn decDoubleClass(src: *const d64) -> Class; + fn decDoubleDigits(src: *const d64) -> uint32_t; + fn decDoubleIsCanonical(src: *const d64) -> uint32_t; + fn decDoubleIsFinite(src: *const d64) -> uint32_t; + fn decDoubleIsInteger(src: *const d64) -> uint32_t; + fn decDoubleIsLogical(src: *const d64) -> uint32_t; + fn decDoubleIsInfinite(src: *const d64) -> uint32_t; + fn decDoubleIsNaN(src: *const d64) -> uint32_t; + fn decDoubleIsNegative(src: *const d64) -> uint32_t; + fn decDoubleIsNormal(src: *const d64) -> uint32_t; + fn decDoubleIsPositive(src: *const d64) -> uint32_t; + fn decDoubleIsSignaling(src: *const d64) -> uint32_t; + fn decDoubleIsSigned(src: *const d64) -> uint32_t; + fn decDoubleIsSubnormal(src: *const d64) -> uint32_t; + fn decDoubleIsZero(src: *const d64) -> uint32_t; + // decNumber stuff. + fn decimal64FromNumber(res: *mut d64, src: *const decNumber, ctx: *mut Context) -> *mut d64; + fn decimal64ToNumber(src: *const d64, res: *mut decNumber) -> *mut decNumber; + fn decNumberPower(res: *mut decNumber, + lhs: *const decNumber, + rhs: *const decNumber, + ctx: *mut Context) + -> *mut decNumber; + fn decNumberExp(res: *mut decNumber, + lhs: *const decNumber, + rhs: *const decNumber, + ctx: *mut Context) + -> *mut decNumber; + fn decNumberLn(res: *mut decNumber, + rhs: *const decNumber, + ctx: *mut Context) + -> *mut decNumber; + fn decNumberLog10(res: *mut decNumber, + rhs: *const decNumber, + ctx: *mut Context) + -> *mut decNumber; +} + +#[allow(unused)] +#[cfg(test)] +mod tests { + #[cfg(any(feature = "ord_subset", feature = "rustc-serialize"))] + use super::*; + #[cfg(any(feature = "ord_subset", feature = "serde"))] + use std::collections::BTreeMap; + + #[cfg(feature = "ord_subset")] + use ord_subset; + + #[cfg(feature = "rustc-serialize")] + use rustc_serialize::json; + + #[cfg(feature = "serde")] + use serde_json::{from_str, to_string}; + + use rand::{self, Rng}; + use rand::distributions::{IndependentSample, Range}; + + #[allow(unused_imports)] + use test::{black_box, Bencher}; + + macro_rules! d128 { + ($lit:expr) => {{ + use std::str::FromStr; + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d128::from_str(&clean).expect("Invalid decimal float literal") + }} + } + + macro_rules! d64 { + ($lit:expr) => {{ + use std::str::FromStr; + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d64::from_str(&clean).expect("Invalid decimal float literal") + }} + } + + #[test] + fn dectest_failing_ddcan_302() { + //println!(); + //println!("{:x}", d64!(1.2345)); + //println!("{:x}", d64!(8646910459111537919)); + let s = "77ffff3fcff3fcff"; + let d = d64::from_hex(s); + let hex = format!("{:x}", d); + assert_eq!(s, hex); + } + + #[test] + fn from_d128() { + assert_eq!(d64::from(d128!(1)), d64!(1)); + assert_eq!(d64::from(d128!(1.23456)), d64!(1.23456)); + } + + #[bench] + fn sums_vec_of_100_000(b: &mut Bencher) { + let x = d64!(0.00012345); + let mut xs: Vec = Vec::with_capacity(100_000); + for i in 0..100_000u32 { + xs.push(d64::from(i) * x); + } + + b.iter(|| { + xs.iter().sum::() + }); + } + + #[test] + fn it_parses_zero_in_exp_notation() { + assert_eq!(d64::from_str("0E-8").unwrap(), d64!(0.00000000)); + } + + #[test] + fn it_verifies_infinity_fns() { + assert!(d64::infinity().is_infinite()); + assert!(!d64::infinity().is_negative()); + assert!(d64::neg_infinity().is_infinite()); + assert!(d64::neg_infinity().is_negative()); + assert_eq!(d64::infinity() + d64!(1), d64::infinity()); + } + + #[test] + fn test_sum_impl() { + let decimals = vec![d64!(1), d64!(2), d64!(3), d64!(4)]; + assert_eq!(d64!(10), decimals.iter().sum()); + assert_eq!(d64!(10), decimals.into_iter().sum()); + } + + #[test] + fn it_checks_default_is_zero() { + assert_eq!(d64::default(), d64::zero()); + } + + #[test] + fn it_handles_a_real_world_small_number_that_landed_in_db_as_nan() { + let amt = d64!(1E-8); + let price = d64!(0.00143500); + let fee = d64!(1E-8); + let total = d64!(0E-8); + assert_eq!(d64::zero(), total); + let as_calculated = (d64!(1) - fee / total).quantize(d64!(0.00000001)); + assert!(as_calculated.is_nan()); + let fixed = (d64!(1) - fee / total.max(d64!(0.00000001))).quantize(d64!(0.00000001)); + assert!(fixed.is_finite()); + } + + #[test] + fn it_checks_the_max_of_nan_and_a_real_number_is_the_real_number() { + let x = d64!(NaN); + assert!(x.is_nan()); + assert_eq!(x.max(d64::zero()), d64::zero()); + assert_eq!(x.max(d64!(-100)), d64!(-100)); + } + + #[bench] + fn random_number_via_u32_range(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + let range = Range::new(980_000_000u32, 1_200_000_000u32); + let e = d64!(1_000_000_000); + b.iter(|| { + let d: d64 = d64::from(range.ind_sample(&mut rng)) / e; + d + }); + } + + #[test] + fn it_validates_range_of_random_number_via_u32_range() { + let mut rng = rand::thread_rng(); + let range = Range::new(980_000_000u32, 1_200_000_000u32); + let e = d64!(1_000_000_000); + let d: d64 = d64::from(range.ind_sample(&mut rng)) / e; + println!("d={}", d); + assert!(d >= d64!(0.98)); + assert!(d <= d64!(1.2)); + } + + #[bench] + fn random_number_via_u32(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d64 = rng.gen::().into(); + d + }); + } + + // #[bench] + // fn random_number_via_u32(b: &mut Bencher) { + // let mut rng = rand::thread_rng(); + // b.iter(|| { + // let d: d64 = rng.gen::().into(); + // d + // }); + // } + + #[test] + fn test_deref_does_not_blow_the_machine_up() { + fn add(a: &d64, b: &d64) -> d64 { + *a + *b + } + let a = d64!(1); + let b = d64!(1); + let c = add(&a, &b); + assert_eq!(c, d64!(2)); + } + + #[test] + fn test_deref_mutate() { + let a = &mut d64!(1.011); + *a += d64!(1.022); + assert_eq!(a, &d64!(2.033)); + } + + #[test] + fn default() { + assert_eq!(d64::zero(), d64::default()); + assert_eq!(d64::zero(), Default::default()); + } + + #[test] + fn special() { + assert!(d64::infinity().is_infinite()); + assert!(!d64::infinity().is_negative()); + + assert!(d64::neg_infinity().is_infinite()); + assert!(d64::neg_infinity().is_negative()); + + assert_eq!(d64::infinity() + d64!(1), d64::infinity()); + } + + #[cfg(feature = "ord_subset")] + #[test] + #[should_panic] + fn test_ord_subset_nan() { + ord_subset::OrdVar::new(d64!(NaN)); + } + + #[cfg(feature = "ord_subset")] + #[test] + #[should_panic] + fn test_ord_subset_qnan() { + ord_subset::OrdVar::new(d64!(qNaN)); + } + + #[cfg(feature = "ord_subset")] + #[test] + fn test_ord_subset_zero() { + assert_eq!(*ord_subset::OrdVar::new(d64::zero()), d64::zero()); + } + + #[cfg(feature = "ord_subset")] + #[test] + fn test_into_for_btreemap() { + let mut m = BTreeMap::, i64>::new(); + m.insert(d64!(1.1).into(), 1); + assert_eq!(m[&d64!(1.1).into()], 1); + } + + #[cfg(feature = "rustc-serialize")] + #[test] + fn test_rustc_serialize() { + #[derive(RustcDecodable, RustcEncodable, PartialEq, Debug)] + struct Test { + price: d64, + }; + let a = Test { price: d64!(12.3456) }; + assert_eq!(json::encode(&a).unwrap(), "{\"price\":\"12.3456\"}"); + let b = json::decode("{\"price\":\"12.3456\"}").unwrap(); + assert_eq!(a, b); + } + + #[cfg(feature = "serde")] + #[test] + fn test_serde() { + let mut a = BTreeMap::new(); + a.insert("price".to_string(), d64!(432.232)); + a.insert("amt".to_string(), d64!(9.9)); + assert_eq!(&to_string(&a).unwrap(), + "{\"amt\":\"9.9\",\"price\":\"432.232\"}"); + let b = from_str("{\"price\":\"432.232\",\"amt\":\"9.9\"}").unwrap(); + assert_eq!(a, b); + } + + #[test] + fn unary_op() { + assert_eq!(d64!(-1.1), -d64!(1.1)); + assert_eq!(d64!(-1.1), -&d64!(1.1)); + } + + #[test] + fn binary_op() { + assert_eq!(d64!(3.33), d64!(1.11) + d64!(2.22)); + assert_eq!(d64!(3.33), &d64!(1.11) + d64!(2.22)); + assert_eq!(d64!(3.33), d64!(1.11) + &d64!(2.22)); + assert_eq!(d64!(3.33), &d64!(1.11) + &d64!(2.22)); + assert_eq!(d64!(5) << 2, d64!(500)); + assert_eq!(d64!(500) >> 1, d64!(50)); + } + + #[test] + fn assign_op() { + let mut x = d64!(1); + x += d64!(2); + assert_eq!(x, d64!(3)); + x *= d64!(3); + assert_eq!(x, d64!(9)); + x -= d64!(1); + assert_eq!(x, d64!(8)); + x /= d64!(16); + assert_eq!(x, d64!(0.5)); + x <<= 2; + assert_eq!(x, d64!(50)); + x >>= 1; + assert_eq!(x, d64!(5)); + } + + #[test] + fn as_ref_operand() { + assert_eq!(d64!(1.1), d64!(1.1).min(d64!(2.2))); + assert_eq!(d64!(1.1), d64!(1.1).min(&d64!(2.2))); + } + + #[test] + fn from_i64() { + assert_eq!(d64::from_str(&::std::i64::MAX.to_string()).unwrap(), + d64::from(::std::i64::MAX)); + assert_eq!(d64::from(0i32), d64::from(0i64)); + assert_eq!(d64::from_str(&(::std::i64::MIN).to_string()).unwrap(), + d64::from(::std::i64::MIN)); + } + + #[test] + fn from_u64() { + assert_eq!(d64::from_str(&::std::u64::MAX.to_string()).unwrap(), + d64::from(::std::u64::MAX)); + assert_eq!(d64::from(0i32), d64::from(0u64)); + assert_eq!(d64::from_str(&(::std::u64::MIN).to_string()).unwrap(), + d64::from(::std::u64::MIN)); + } + + #[test] + fn test_sum() { + let decimals = vec![d64!(1), d64!(2), d64!(3), d64!(4)]; + + assert_eq!(d64!(10), decimals.iter().sum()); + + assert_eq!(d64!(10), decimals.into_iter().sum()); + } + + #[bench] + fn d64_via_random_u64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d64 = rng.gen::().into(); + d + }); + } + + #[bench] + fn d64_via_random_i64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d64 = rng.gen::().into(); + d + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index feea0923..2b092511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ +#![feature(const_fn)] +#![cfg_attr(test, feature(test))] +#![allow(deprecated)] + #[macro_use] extern crate bitflags; extern crate libc; @@ -10,31 +14,24 @@ extern crate serde; #[cfg(feature = "serde")] #[cfg(test)] extern crate serde_json; - -#[macro_export] -/// A macro to construct d128 literals. -/// -/// # Examples: -/// ``` -/// # #[macro_use] -/// # extern crate decimal; -/// # fn main() { -/// assert!(d128!(NaN).is_nan()); -/// assert!(d128!(0).is_zero()); -/// assert!(d128!(-0.1).is_negative()); -/// # } -/// ``` -macro_rules! d128 { - ($lit:expr) => {{ - use std::str::FromStr; - $crate::d128::from_str(stringify!($lit)).expect("Invalid decimal float literal") - }} -} +#[cfg(test)] +extern crate rand; +#[cfg(test)] +extern crate test; +#[cfg(feature = "faster")] +extern crate faster; +#[cfg(feature = "slog")] +extern crate slog; +#[cfg(test)] +#[macro_use] +extern crate approx; mod context; mod dec128; +mod dec64; -pub use dec128::d128; +pub use crate::dec128::d128; +pub use crate::dec64::d64; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] @@ -76,8 +73,10 @@ bitflags! { /// # Examples /// /// ``` - /// # #[macro_use] + /// # #![feature(proc_macro_hygiene)] + /// # extern crate decimal_macros; /// # extern crate decimal; + /// # use decimal_macros::*; /// # use decimal::d128; /// # use decimal::Status; /// # fn main() { diff --git a/src/bin/run-test.rs b/src/run_test.rs similarity index 67% rename from src/bin/run-test.rs rename to src/run_test.rs index 72e0bba0..ebb9c049 100644 --- a/src/bin/run-test.rs +++ b/src/run_test.rs @@ -1,12 +1,19 @@ +#![allow(unused)] +#![allow(deprecated)] + extern crate decimal; use decimal::*; -use std::fmt; +use std::fmt::{self, Debug, Display, LowerHex, LowerExp}; +use std::str::FromStr; use std::fs::File; use std::path::Path; use std::io::BufRead; use std::io::BufReader; +const TEST_D128: bool = true; +const TEST_D64: bool = true; + fn find_end_quote(s: &str, quote: char) -> Option { match s.find(quote) { None => None, @@ -82,7 +89,7 @@ enum Directive<'a> { Test(&'a str), } -#[derive(Debug)] +#[derive(Debug, Clone)] enum Op<'a> { Abs(&'a str), Add(&'a str, &'a str), @@ -137,7 +144,7 @@ enum Op<'a> { Xor(&'a str, &'a str), } -#[derive(Debug)] +#[derive(Debug, Clone)] struct Test<'a> { raw: &'a str, id: &'a str, @@ -374,9 +381,16 @@ fn read_test(path: &Path) { env.process_directive(path, directive); } Instr::Test(test) => { - let result = run_test(&env, test); - summary.add(&result); - println!("{}", result); + if TEST_D128 { + let result = run_test(&env, test.clone()); + println!("[d128] {}", result); + summary.add(&result); + } + if TEST_D64 { + let result = run_test_d64(&env, test); + println!("[d64 ] {}", result); + summary.add(&result); + } } } } @@ -399,36 +413,29 @@ impl<'a> fmt::Display for TestResult<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { TestResult::Pass(ref test) => { - try!(write!(fmt, "[ PASS ] {}", test.raw)); + r#try!(write!(fmt, "[ PASS ] {}", test.raw)); } TestResult::Fail(ref test, ref actual, ref status) => { let exp_flags = format!("{:?}", test.expected_status); let act_flags = format!("{:?}", status); - try!(write!(fmt, "[ FAIL ] {}\n", test.raw)); - try!(write!(fmt, + r#try!(write!(fmt, "[ FAIL ] {}\n", test.raw)); + r#try!(write!(fmt, "\tEXPECTED: {:<43} {:<43}\n", test.expected_value, exp_flags)); - try!(write!(fmt, "\t ACTUAL: {:<43} {:<43}", actual, act_flags)); + r#try!(write!(fmt, "\t ACTUAL: {:<43} {:<43}", actual, act_flags)); } TestResult::Ignored(ref test) => { - try!(write!(fmt, "[ IGNORED ] {}", test.raw)); + r#try!(write!(fmt, "[ IGNORED ] {}", test.raw)); } } Ok(()) } } -fn parse_operand(s: &str) -> d128 { - if s.chars().nth(0) == Some('#') { - d128::from_hex(&s[1..]) - } else { - use std::str::FromStr; - d128::from_str(s).expect("Invalid decimal") - } -} - -fn format_result<'a>(value: d128, test: &Test<'a>) -> String { +fn format_result<'a, T>(value: T, test: &Test<'a>) -> String + where T: Display + LowerHex + LowerExp +{ if test.expected_value.chars().nth(0) == Some('#') { format!("#{:x}", value) } else if let Op::ToEng(..) = test.op { @@ -438,14 +445,40 @@ fn format_result<'a>(value: d128, test: &Test<'a>) -> String { } } +trait FromHex { + fn from_hex(_: &str) -> Self; +} + +impl FromHex for d128 { + fn from_hex(s: &str) -> Self { d128::from_hex(s) } +} + +impl FromHex for d64 { + fn from_hex(s: &str) -> Self { d64::from_hex(s) } +} + +fn parse_operand(s: &str) -> T + where T: FromHex + FromStr, + E: Debug +{ + if s.chars().nth(0) == Some('#') { + T::from_hex(&s[1..]) + } else { + use std::str::FromStr; + T::from_str(s).expect("Invalid decimal") + } +} + macro_rules! simple_op { - ($test:ident, $res:ident = $func:ident($($arg:ident),+)) => { + ($d:ty, $test:ident, $res:ident = $func:ident($($arg:ident),+)) => { { + type D = $d; + $( if $arg == "#" { return TestResult::Ignored($test); } - let $arg = parse_operand($arg); + let $arg: D = parse_operand($arg); )+ - $res = format_result(d128::$func($($arg),+), &$test); + $res = format_result(D::$func($($arg),+), &$test); } }; } @@ -470,61 +503,61 @@ fn run_test<'a>(env: &Environment, test: Test<'a>) -> TestResult<'a> { let status: Status; d128::set_status(Status::empty()); match test.op { - Op::Abs(a) => simple_op!(test, value = abs(a)), - Op::Add(a, b) => simple_op!(test, value = add(a, b)), - Op::And(a, b) => simple_op!(test, value = bitand(a, b)), + Op::Abs(a) => simple_op!(d128, test, value = abs(a)), + Op::Add(a, b) => simple_op!(d128, test, value = add(a, b)), + Op::And(a, b) => simple_op!(d128, test, value = bitand(a, b)), Op::Apply(a) => { if a == "#" { return TestResult::Ignored(test); } - value = format_result(parse_operand(a), &test); + value = format_result(parse_operand::(a), &test); } - Op::Canonical(a) => simple_op!(test, value = canonical(a)), + Op::Canonical(a) => simple_op!(d128, test, value = canonical(a)), Op::Compare(a, b) => { if a == "#" || b == "#" { return TestResult::Ignored(test); } - value = format_result(d128::compare(&parse_operand(a), &parse_operand(b)), &test); + value = format_result(d128::compare(&parse_operand(a), &parse_operand::(b)), &test); } Op::CompareTotal(a, b) => { if a == "#" || b == "#" { return TestResult::Ignored(test); } - value = format_result(d128::compare_total(&parse_operand(a), &parse_operand(b)), + value = format_result(d128::compare_total(&parse_operand(a), &parse_operand::(b)), &test); } - Op::Divide(a, b) => simple_op!(test, value = div(a, b)), - Op::Fma(a, b, c) => simple_op!(test, value = mul_add(a, b, c)), - Op::Invert(a) => simple_op!(test, value = not(a)), - Op::LogB(a) => simple_op!(test, value = logb(a)), - Op::Max(a, b) => simple_op!(test, value = max(a, b)), - Op::Min(a, b) => simple_op!(test, value = min(a, b)), - Op::Minus(a) => simple_op!(test, value = neg(a)), - Op::Multiply(a, b) => simple_op!(test, value = mul(a, b)), - Op::NextMinus(a) => simple_op!(test, value = previous(a)), - Op::NextPlus(a) => simple_op!(test, value = next(a)), - Op::NextToward(a, b) => simple_op!(test, value = towards(a, b)), - Op::Or(a, b) => simple_op!(test, value = bitor(a, b)), - Op::Power(a, b) => simple_op!(test, value = pow(a, b)), - Op::Quantize(a, b) => simple_op!(test, value = quantize(a, b)), - Op::Reduce(a) => simple_op!(test, value = reduce(a)), - Op::Remainder(a, b) => simple_op!(test, value = rem(a, b)), - Op::Rotate(a, b) => simple_op!(test, value = rotate(a, b)), - Op::ScaleB(a, b) => simple_op!(test, value = scaleb(a, b)), - Op::Subtract(a, b) => simple_op!(test, value = sub(a, b)), + Op::Divide(a, b) => simple_op!(d128, test, value = div(a, b)), + Op::Fma(a, b, c) => simple_op!(d128, test, value = mul_add(a, b, c)), + Op::Invert(a) => simple_op!(d128, test, value = not(a)), + Op::LogB(a) => simple_op!(d128, test, value = logb(a)), + Op::Max(a, b) => simple_op!(d128, test, value = max(a, b)), + Op::Min(a, b) => simple_op!(d128, test, value = min(a, b)), + Op::Minus(a) => simple_op!(d128, test, value = neg(a)), + Op::Multiply(a, b) => simple_op!(d128, test, value = mul(a, b)), + Op::NextMinus(a) => simple_op!(d128, test, value = previous(a)), + Op::NextPlus(a) => simple_op!(d128, test, value = next(a)), + Op::NextToward(a, b) => simple_op!(d128, test, value = towards(a, b)), + Op::Or(a, b) => simple_op!(d128, test, value = bitor(a, b)), + Op::Power(a, b) => simple_op!(d128, test, value = pow(a, b)), + Op::Quantize(a, b) => simple_op!(d128, test, value = quantize(a, b)), + Op::Reduce(a) => simple_op!(d128, test, value = reduce(a)), + Op::Remainder(a, b) => simple_op!(d128, test, value = rem(a, b)), + Op::Rotate(a, b) => simple_op!(d128, test, value = rotate(a, b)), + Op::ScaleB(a, b) => simple_op!(d128, test, value = scaleb(a, b)), + Op::Subtract(a, b) => simple_op!(d128, test, value = sub(a, b)), Op::ToEng(a) => { if a == "#" { return TestResult::Ignored(test); } - value = format_result(parse_operand(a), &test); + value = format_result(parse_operand::(a), &test); } Op::ToSci(a) => { if a == "#" { return TestResult::Ignored(test); } - value = format_result(parse_operand(a), &test); + value = format_result(parse_operand::(a), &test); } - Op::Xor(a, b) => simple_op!(test, value = bitxor(a, b)), + Op::Xor(a, b) => simple_op!(d128, test, value = bitxor(a, b)), _ => { return TestResult::Ignored(test); } @@ -537,7 +570,96 @@ fn run_test<'a>(env: &Environment, test: Test<'a>) -> TestResult<'a> { } } +fn run_test_d64<'a>(env: &Environment, test: Test<'a>) -> TestResult<'a> { + use std::ops::*; + + let d64_env = Environment { + precision: Some(16), + rounding: Some(Rounding::HalfEven), + max_exponent: Some(384), + min_exponent: Some(-383), + extended: true, + clamp: true, + }; + + if *env != d64_env { + return TestResult::Ignored(test); + } + + let value: String; + let status: Status; + d64::set_status(Status::empty()); + match test.op { + Op::Abs(a) => simple_op!(d64, test, value = abs(a)), + Op::Add(a, b) => simple_op!(d64, test, value = add(a, b)), + Op::And(a, b) => simple_op!(d64, test, value = bitand(a, b)), + Op::Apply(a) => { + if a == "#" { + return TestResult::Ignored(test); + } + value = format_result(parse_operand::(a), &test); + } + Op::Canonical(a) => simple_op!(d64, test, value = canonical(a)), + Op::Compare(a, b) => { + if a == "#" || b == "#" { + return TestResult::Ignored(test); + } + value = format_result(d64::compare(&parse_operand(a), &parse_operand::(b)), &test); + } + Op::CompareTotal(a, b) => { + if a == "#" || b == "#" { + return TestResult::Ignored(test); + } + value = format_result(d64::compare_total(&parse_operand(a), &parse_operand::(b)), + &test); + } + Op::Divide(a, b) => simple_op!(d64, test, value = div(a, b)), + Op::Fma(a, b, c) => simple_op!(d64, test, value = mul_add(a, b, c)), + Op::Invert(a) => simple_op!(d64, test, value = not(a)), + Op::LogB(a) => simple_op!(d64, test, value = logb(a)), + Op::Max(a, b) => simple_op!(d64, test, value = max(a, b)), + Op::Min(a, b) => simple_op!(d64, test, value = min(a, b)), + Op::Minus(a) => simple_op!(d64, test, value = neg(a)), + Op::Multiply(a, b) => simple_op!(d64, test, value = mul(a, b)), + Op::NextMinus(a) => simple_op!(d64, test, value = previous(a)), + Op::NextPlus(a) => simple_op!(d64, test, value = next(a)), + Op::NextToward(a, b) => simple_op!(d64, test, value = towards(a, b)), + Op::Or(a, b) => simple_op!(d64, test, value = bitor(a, b)), + Op::Power(a, b) => simple_op!(d64, test, value = pow(a, b)), + Op::Quantize(a, b) => simple_op!(d64, test, value = quantize(a, b)), + Op::Reduce(a) => simple_op!(d64, test, value = reduce(a)), + Op::Remainder(a, b) => simple_op!(d64, test, value = rem(a, b)), + Op::Rotate(a, b) => simple_op!(d64, test, value = rotate(a, b)), + Op::ScaleB(a, b) => simple_op!(d64, test, value = scaleb(a, b)), + Op::Subtract(a, b) => simple_op!(d64, test, value = sub(a, b)), + Op::ToEng(a) => { + if a == "#" { + return TestResult::Ignored(test); + } + value = format_result(parse_operand::(a), &test); + } + Op::ToSci(a) => { + if a == "#" { + return TestResult::Ignored(test); + } + value = format_result(parse_operand::(a), &test); + } + Op::Xor(a, b) => simple_op!(d64, test, value = bitxor(a, b)), + _ => { + return TestResult::Ignored(test); + } + } + status = d64::get_status(); + if value == test.expected_value && status == test.expected_status { + TestResult::Pass(test) + } else { + TestResult::Fail(test, value, status) + } +} + fn main() { + println!("testing d128: {}", TEST_D128); + println!("testing d64: {}", TEST_D64); let filepath = std::env::args().nth(1).expect("Filename to test"); let path = Path::new(&filepath); read_test(path); diff --git a/src/to_string.rs b/src/to_string.rs new file mode 100644 index 00000000..2c039fd4 --- /dev/null +++ b/src/to_string.rs @@ -0,0 +1,30 @@ +extern crate decimal; +extern crate clap; +use std::str::FromStr; + +fn main() { + let args: clap::ArgMatches = clap::App::new("to-string") + .version("0.1") + .arg(clap::Arg::with_name("precision") + .short("r") + .long("round") + .takes_value(true) + .required(false) + .help("round to ")) + .arg(clap::Arg::with_name("decimal-literal") + .help("Decimal float literal to show string for") + .required(true)) + .get_matches(); + + let literal = args.value_of("decimal-literal").unwrap(); + + let mut d = decimal::d128::from_str(&literal).expect("Invalid float literal"); + + if let Some(q) = args.value_of("precision") { + let q = decimal::d128::from_str(q).expect("failed to parse --round argument"); + assert!(q.is_finite(), "parsed --round argument is not finite: {}", q); + d = d.round(q); + } + + println!("{}", d); +}