From fde8285281e32c8c0f87fbe0c1a4dd746e4358a8 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 22 Jun 2017 00:18:48 -0400 Subject: [PATCH 01/75] impl Deref for d128 (I *think* it's right) --- src/dec128.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/dec128.rs b/src/dec128.rs index 33b0cd4b..6c7e9df4 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -18,7 +18,7 @@ use std::hash::{Hash, Hasher}; use std::mem::uninitialized; 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::num::FpCategory; @@ -177,6 +177,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 @@ -978,6 +986,24 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; + #[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()); From 0cfa23e55b78ddb9b7e824c0669a6c7669c8fd8e Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 27 Jul 2017 15:44:19 -0400 Subject: [PATCH 02/75] converts d128::from_raw_bytes to const fn --- src/dec128.rs | 2 +- src/lib.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dec128.rs b/src/dec128.rs index 6c7e9df4..141af826 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -499,7 +499,7 @@ impl d128 { } /// Creates a d128 from raw bytes. Endianess is host dependent. - pub unsafe fn from_raw_bytes(bytes: [u8; 16]) -> d128 { + pub const unsafe fn from_raw_bytes(bytes: [u8; 16]) -> d128 { d128 { bytes: bytes } } diff --git a/src/lib.rs b/src/lib.rs index c52c70a5..8944f2b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(const_fn)] + #[macro_use] extern crate bitflags; extern crate libc; From 595257cb22a68e717d80aed6376bf0a6b7796bd1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 27 Jul 2017 15:44:27 -0400 Subject: [PATCH 03/75] adds swp files to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 94408df6..54631b60 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ # Generated by Cargo /target/ Cargo.lock +.*.swp From a028505e3dc1bbf751aacb3696406f24f60c09d1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 27 Jul 2017 15:45:03 -0400 Subject: [PATCH 04/75] adds "bytes" binary to easily extract the bytes for consts --- Cargo.toml | 11 +++++++++++ src/get_bytes.rs | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/get_bytes.rs diff --git a/Cargo.toml b/Cargo.toml index 8dd006b1..f139a35b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,23 @@ documentation = "https://alkis.github.io/decimal" keywords = ["decimal", "decNumber"] license = "Apache-2.0" +[lib] +name = "decimal" +path = "src/lib.rs" + +[[bin]] +path = "src/get_bytes.rs" +name = "bytes" + + + [dependencies] bitflags = "~0.7" libc = "~0.2" ord_subset = { optional = true, version = "~1.2" } rustc-serialize = { optional = true, version = "~0.3" } serde = { optional = true, version = "1" } +clap = { features = [], version = "2" } [features] default = ["ord_subset", "rustc-serialize", "serde"] diff --git a/src/get_bytes.rs b/src/get_bytes.rs new file mode 100644 index 00000000..a28cac29 --- /dev/null +++ b/src/get_bytes.rs @@ -0,0 +1,17 @@ +extern crate decimal; +extern crate clap; +use std::str::FromStr; + +fn main() { + let args: clap::ArgMatches = clap::App::new("bytes") + .version("0.1") + .arg(clap::Arg::with_name("dec_literal") + .help("Decimal float literal to show bytes for") + .required(true)) + .get_matches(); + + let literal = args.value_of("dec_literal").unwrap(); + let d = decimal::d128::from_str(&literal).expect("Invalid float literal"); + println!("Bytes for {}", d); + println!("{:?}", d.to_raw_bytes()); +} From ca665435e3a84b920bc518875bb9af6965cd75bf Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 18 Aug 2017 01:37:15 -0400 Subject: [PATCH 05/75] disables gcc warnings that arrived with the new version of the gcc crate --- build.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 6044376d..6fe5ba7f 100644 --- a/build.rs +++ b/build.rs @@ -6,7 +6,8 @@ fn main() { } else { "0" }; - gcc::Config::new() + gcc::Build::new() + .warnings(false) .include("decNumber") .file("decNumber/decContext.c") .file("decNumber/decDouble.c") From c40dd700cb39688de23c0297f367d63d16f6cf3c Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 8 Nov 2017 12:26:12 -0500 Subject: [PATCH 06/75] adds decimal-macros plugin workspace and merges upstream v2.0.0 changes - removed unsafe designation for `from_raw_bytes` because otherwise every use of the d128! plugin macro would need an unsafe. - macro can be used to create a `const A: d128 = d128!(1);` - decimal-macros has basic tests but they don't run when `cargo test` is called from the root project dir (e.g. ~/src/decimal). You need to navigate up to the decimal-macros dir to run them. --- .gitignore | 4 +- Cargo.toml | 17 +++-- decimal-macros/Cargo.toml | 17 +++++ decimal-macros/src/lib.rs | 92 ++++++++++++++++++++++++ decimal-macros/tests/test.rs | 34 +++++++++ decimal-macros/tests/without_const_fn.rs | 28 ++++++++ src/dec128.rs | 6 +- src/lib.rs | 24 +++---- 8 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 decimal-macros/Cargo.toml create mode 100644 decimal-macros/src/lib.rs create mode 100644 decimal-macros/tests/test.rs create mode 100644 decimal-macros/tests/without_const_fn.rs diff --git a/.gitignore b/.gitignore index 54631b60..a6112e64 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ # Generated by Cargo /target/ Cargo.lock -.*.swp +*.swp +*.swo +/decimal-macros/target/ diff --git a/Cargo.toml b/Cargo.toml index 37db9043..0c43b074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "1.0.1" +version = "2.0.0" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" @@ -17,21 +17,20 @@ path = "src/lib.rs" path = "src/get_bytes.rs" name = "bytes" - - [dependencies] -bitflags = "~0.7" -libc = "~0.2" -ord_subset = { optional = true, version = "~1.2" } -rustc-serialize = { optional = true, version = "~0.3" } + +bitflags = "1" +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" } [features] -default = ["ord_subset", "rustc-serialize", "serde"] +default = ["ord_subset", "serde"] [build-dependencies] -gcc = "~0.3" +gcc = "0.3" [dev-dependencies] serde_json = "1" diff --git a/decimal-macros/Cargo.toml b/decimal-macros/Cargo.toml new file mode 100644 index 00000000..cf790dc1 --- /dev/null +++ b/decimal-macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "decimal-macros" +version = "0.1.0" +authors = ["Callum Tolley "] + +[lib] +name = "decimal_macros" +plugin = true + +[dependencies] +decimal = { path = "../" } +libc = "~0.2" + +[build-dependencies] +gcc = "0.3" + +[workspace] diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs new file mode 100644 index 00000000..98fdd0a2 --- /dev/null +++ b/decimal-macros/src/lib.rs @@ -0,0 +1,92 @@ +#![feature(plugin_registrar, 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}; +use syntax::ext::build::AstBuilder; +use syntax::ext::source_util; +use syntax::codemap::Span; +use syntax::ast::{ExprKind, LitKind, StrStyle}; +use syntax::tokenstream::TokenTree; + +use decimal::d128; + +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); + } + let num = match from_str(s.as_str().as_ref()) { + Ok(num) => num, + Err(s) => { + cx.span_err(lit.span, s); + return DummyResult::any(sp); + } + }; + let num = unsafe { ::std::mem::transmute::(num) }; + + // 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) +} + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_macro("d128", d128_lit) +} + +fn from_str(s: &str) -> Result { + use std::str::FromStr; + d128::set_status(decimal::Status::empty()); + let res = d128::from_str(s); + + let status = d128::get_status(); + if status.contains(decimal::Status::CONVERSION_SYNTAX) { + Err("not a valid d128 number") + } else if status.contains(decimal::Status::OVERFLOW) { + Err("too large for a d128 number") + } else if status.contains(decimal::Status::UNDERFLOW) { + Err("too small for a d128 number") + } else if !status.is_empty() { + Err("not a valid d128 number") + } else { + Ok(res.unwrap()) + } +} diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs new file mode 100644 index 00000000..504d725c --- /dev/null +++ b/decimal-macros/tests/test.rs @@ -0,0 +1,34 @@ +#![feature(plugin, const_fn)] +#![plugin(decimal_macros)] +extern crate decimal; + +use decimal::d128; + +#[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), d128::from(1) / d128::from(10)); +} + +#[test] +fn zero_eq_zero() { + assert_eq!(d128!(0), d128::zero()); +} + +#[test] +fn create_d128_const() { + const ZERO: d128 = d128!(0); + assert_eq!(ZERO, d128::zero()); +} + diff --git a/decimal-macros/tests/without_const_fn.rs b/decimal-macros/tests/without_const_fn.rs new file mode 100644 index 00000000..aa500045 --- /dev/null +++ b/decimal-macros/tests/without_const_fn.rs @@ -0,0 +1,28 @@ +#![feature(plugin)] +#![plugin(decimal_macros)] +extern crate decimal; + +use decimal::d128; + +#[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), d128::from(1) / d128::from(10)); +} + +#[test] +fn zero_eq_zero() { + assert_eq!(d128!(0), d128::zero()); +} + diff --git a/src/dec128.rs b/src/dec128.rs index 141af826..34295082 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -497,10 +497,10 @@ impl d128 { { CTX.with(|ctx| f(&mut ctx.borrow_mut())) } - + /// Creates a d128 from raw bytes. Endianess is host dependent. - pub const unsafe fn from_raw_bytes(bytes: [u8; 16]) -> d128 { - d128 { bytes: bytes } + pub const fn from_raw_bytes(bytes: [u8; 16]) -> d128 { + d128 { bytes } } /// Returns raw bytes for this d128. Endianess is host dependent. diff --git a/src/lib.rs b/src/lib.rs index 8944f2b6..270bbbd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,34 +85,34 @@ bitflags! { /// # fn main() { /// assert_eq!(d128::get_status(), Status::empty()); /// let _ = d128!(1) / &d128!(0); - /// assert!(d128::get_status().contains(decimal::DIVISION_BY_ZERO)); + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); /// let _ = d128!(0) / &d128!(0); - /// assert!(d128::get_status().contains(decimal::DIVISION_UNDEFINED)); + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_UNDEFINED)); /// // The previous status flag was not cleared! - /// assert!(d128::get_status().contains(decimal::DIVISION_BY_ZERO)); + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); /// # } - pub flags Status: ::libc::uint32_t { + pub struct Status: ::libc::uint32_t { /// Conversion syntax error. - const CONVERSION_SYNTAX = 0x00000001, + const CONVERSION_SYNTAX = 0x00000001; /// Division by zero. - const DIVISION_BY_ZERO = 0x00000002, + const DIVISION_BY_ZERO = 0x00000002; /// Division impossible. - const DIVISION_IMPOSSIBLE = 0x00000004, + const DIVISION_IMPOSSIBLE = 0x00000004; /// Division undefined. - const DIVISION_UNDEFINED = 0x00000008, + const DIVISION_UNDEFINED = 0x00000008; // const INSUFFICIENT_STORAGE = 0x00000010, /// The result is inexact. - const INEXACT = 0x00000020, + const INEXACT = 0x00000020; // const INVALID_CONTEXT = 0x00000040, /// An invalid operation was requested. - const INVALID_OPERATION = 0x00000080, + const INVALID_OPERATION = 0x00000080; // const LOST_DIGITS = 0x00000100, /// Overflow. - const OVERFLOW = 0x00000200, + const OVERFLOW = 0x00000200; // const CLAMPED = 0x00000400, // const ROUNDED = 0x00000800, // const SUBNORMAL = 0x00001000, /// Underflow. - const UNDERFLOW = 0x00002000, + const UNDERFLOW = 0x00002000; } } From 7639aafdb64037b66de05f03e16da39814c0c80c Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 8 Nov 2017 13:54:26 -0500 Subject: [PATCH 07/75] fixes negative numbers bug for plugin --- decimal-macros/src/lib.rs | 12 +++++++----- decimal-macros/tests/without_const_fn.rs | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index 98fdd0a2..381a7464 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -75,17 +75,19 @@ pub fn plugin_registrar(reg: &mut Registry) { fn from_str(s: &str) -> Result { use std::str::FromStr; d128::set_status(decimal::Status::empty()); - let res = d128::from_str(s); + let no_spaces = s.replace(" ", ""); + let res = d128::from_str(&no_spaces); let status = d128::get_status(); if status.contains(decimal::Status::CONVERSION_SYNTAX) { - Err("not a valid d128 number") + 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") + Err("too large for a d128 number (OVERFLOW)") } else if status.contains(decimal::Status::UNDERFLOW) { - Err("too small for a d128 number") + Err("too small for a d128 number (UNDERFLOW)") } else if !status.is_empty() { - Err("not a valid d128 number") + Err("not a valid d128 number (is_empty)") } else { Ok(res.unwrap()) } diff --git a/decimal-macros/tests/without_const_fn.rs b/decimal-macros/tests/without_const_fn.rs index aa500045..b61f5117 100644 --- a/decimal-macros/tests/without_const_fn.rs +++ b/decimal-macros/tests/without_const_fn.rs @@ -3,6 +3,7 @@ extern crate decimal; use decimal::d128; +use std::str::FromStr; #[test] fn basic_plugin_sanity_checks() { @@ -26,3 +27,21 @@ fn zero_eq_zero() { assert_eq!(d128!(0), d128::zero()); } +#[test] +fn it_parses_a_negative_number() { + assert_eq!(d128!(-1), d128::from_str("-1").unwrap()); + assert_eq!(d128!(-0.1), d128::from_str("-0.1").unwrap()); +} + +#[test] +fn it_does_not_parse_a_negative_number_but_we_check_something_else() { + assert_eq!(-d128!(1), d128::from_str("-1").unwrap()); + assert_eq!(-d128!(0.1), d128::from_str("-0.1").unwrap()); +} + +#[test] +fn it_parses_nan() { + assert!(d128!(NaN).is_nan()); +} + + From 7899a2b0f1c602c896417e3f455790594044c62f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 29 Nov 2017 18:27:52 -0500 Subject: [PATCH 08/75] adds f64, f32 options to get_bytes --- src/get_bytes.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/get_bytes.rs b/src/get_bytes.rs index a28cac29..752290ce 100644 --- a/src/get_bytes.rs +++ b/src/get_bytes.rs @@ -8,10 +8,33 @@ fn main() { .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(); - let d = decimal::d128::from_str(&literal).expect("Invalid float literal"); - println!("Bytes for {}", d); - println!("{:?}", d.to_raw_bytes()); + + 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()); + } } From bc82f713128268627b4a05c07f12e0f4c498d546 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 20 Dec 2017 04:24:46 -0500 Subject: [PATCH 09/75] adds `d128::finite_or` and `d128::finite_or_else` modeled after `Option` and friends --- src/dec128.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 34295082..49d68a2c 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -838,6 +838,20 @@ 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() + } + } } extern "C" { From 7f8335bf40f885b935b2b347c839edda4d230a87 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 20 Dec 2017 15:52:24 +0000 Subject: [PATCH 10/75] remove upstream shields/icons --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 1c30bf14..b026e470 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,4 @@ # decimal - -[![Travis](https://img.shields.io/travis/alkis/decimal.svg)](https://img.shields.io/travis/alkis/decimal.svg) -[![Crates.io](https://img.shields.io/crates/d/decimal.svg)](https://img.shields.io/crates/d/decimal.svg) -[![Crates.io](https://img.shields.io/crates/v/decimal.svg)](https://img.shields.io/crates/v/decimal.svg) -[![Crates.io](https://img.shields.io/crates/l/decimal.svg)](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). From f09bfe04ff0067f15b304f9c684bfdc31fb4696a Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 11 Jan 2018 21:24:24 -0500 Subject: [PATCH 11/75] adds test feature/crate --- justfile | 2 ++ src/dec128.rs | 3 +++ src/lib.rs | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 justfile 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/dec128.rs b/src/dec128.rs index f3b37081..47de9caf 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1000,6 +1000,9 @@ mod tests { #[cfg(feature = "serde")] use serde_json::{from_str, to_string}; + #[allow(unused_imports)] + use test::{black_box, Bencher}; + #[test] fn test_deref_does_not_blow_the_machine_up() { fn add(a: &d128, b: &d128) -> d128 { diff --git a/src/lib.rs b/src/lib.rs index f0b891dc..457498bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(const_fn)] +#![feature(const_fn, test)] #[macro_use] extern crate bitflags; @@ -12,6 +12,8 @@ extern crate serde; #[cfg(feature = "serde")] #[cfg(test)] extern crate serde_json; +#[cfg(test)] +extern crate test; #[macro_export] /// A macro to construct d128 literals. From 82e9811334f6107a784f4322ba3dd3c0ef3dfc0f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 11 Jan 2018 21:25:38 -0500 Subject: [PATCH 12/75] some benchmarks on generating random d128s from ints --- Cargo.toml | 1 + src/dec128.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 47 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 53d06540..1b0099a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ cc = "1" [dev-dependencies] serde_json = "1" +rand = "0.4" diff --git a/src/dec128.rs b/src/dec128.rs index 47de9caf..8fc671a2 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1000,9 +1000,53 @@ 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}; + #[test] + #[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 random_number_via_u64(b: &mut Bencher) { + let mut rng = rand::thread_rng(); + b.iter(|| { + let d: d128 = rng.gen::().into(); + d + }); + } + #[test] fn test_deref_does_not_blow_the_machine_up() { fn add(a: &d128, b: &d128) -> d128 { diff --git a/src/lib.rs b/src/lib.rs index 457498bf..8ec1dc94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ extern crate serde; #[cfg(test)] extern crate serde_json; #[cfg(test)] +extern crate rand; +#[cfg(test)] extern crate test; #[macro_export] From ccf9fca5d781b16d84ed2012ecf5c8ee0d083b7a Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 11 Jan 2018 21:26:06 -0500 Subject: [PATCH 13/75] additional tests for some edge cases that came up --- src/dec128.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 8fc671a2..063223fb 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1007,6 +1007,27 @@ mod tests { use test::{black_box, Bencher}; #[test] + #[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(); From e49cfc21df6bbcf142abe67cc0139d3f19267da7 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 11 Jan 2018 21:26:52 -0500 Subject: [PATCH 14/75] impl Sum for d128 where T: Borrow --- src/dec128.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 063223fb..43b12375 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -16,6 +16,8 @@ use std::ffi::{CStr, CString}; use std::fmt; use std::hash::{Hash, Hasher}; use std::mem::uninitialized; +use std::borrow::Borrow; +use std::iter::Sum; 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}; @@ -484,6 +486,14 @@ impl ShrAssign for d128 { } } +impl Sum for d128 where T: Borrow { + fn sum>(iter: I) -> d128 { + iter.into_iter() + .fold(d128::zero(), |acc, val| + acc + val.borrow()) + } +} + impl d128 { fn default_context() -> Context { unsafe { @@ -1007,6 +1017,18 @@ mod tests { use test::{black_box, Bencher}; #[test] + #[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); From d762397169c6e60a0c56ab22ff7c3202a9267076 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 11 Jan 2018 21:27:05 -0500 Subject: [PATCH 15/75] infinity, neg_infinity fns --- src/dec128.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 43b12375..990ad531 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -557,6 +557,16 @@ impl d128 { } } + /// Returns the d128 representing +Infinity. + pub fn infinity() -> d128 { + d128!(Infinity) + } + + /// Returns the d128 representing -Infinity. + pub fn neg_infinity() -> d128 { + d128!(-Infinity) + } + // Computational. /// Returns the absolute value of `self`. @@ -1017,6 +1027,19 @@ mod tests { use test::{black_box, Bencher}; #[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)]; From 09c9a567ff0bd1c61bae065adaaba95325320304 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 29 Jan 2018 21:45:56 -0500 Subject: [PATCH 16/75] Adds truncate, half-up rounding functions This adds - A binding to the `decContextSetRounding` function - A d128 method, `with_rounding` to initialize a `Context` with a given `Rounding` - `ROUND_DOWN` and `HALF_UP` thread local static `Context`s initialized with those roundings - Two methods on d128, `with_round_down` and `with_half_up` that mimic `with_context` but use alternative roundings - Two methods, `truncate` and `round` that offer `quantize` like interfaces to these rounding settings. --- src/dec128.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index e7a37c48..6a357489 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -26,6 +26,8 @@ use std::str::FromStr; use std::str::from_utf8_unchecked; 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)] @@ -494,6 +496,28 @@ impl d128 { } } + /// Initialize a `Context` with the specified `Rounding`. + fn with_rounding(rounding: Rounding) -> Context { + unsafe { + let mut res: Context = uninitialized(); + let mut ctx = *decContextDefault(&mut res, 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 { @@ -694,10 +718,64 @@ 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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 @@ -855,6 +933,7 @@ impl d128 { 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 decQuadFromInt32(res: *mut d128, src: int32_t) -> *mut d128; From 04f867e047430ce0db0c224d081844992af6f32e Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 00:36:38 -0500 Subject: [PATCH 17/75] adds d64, seems to be working --- decimal-macros/src/lib.rs | 221 ++++-- src/dec128.rs | 39 ++ src/dec64.rs | 1361 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 22 + 4 files changed, 1576 insertions(+), 67 deletions(-) create mode 100644 src/dec64.rs diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index 381a7464..ff130329 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -14,81 +14,168 @@ use syntax::codemap::Span; use syntax::ast::{ExprKind, LitKind, StrStyle}; use syntax::tokenstream::TokenTree; -use decimal::d128; +use decimal::{d128, d64}; -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); - } - let num = match from_str(s.as_str().as_ref()) { - Ok(num) => num, - Err(s) => { - cx.span_err(lit.span, s); +// pub use self::d128_lit::*; +// pub use self::d64_lit::*; + +#[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); } - }; - let num = unsafe { ::std::mem::transmute::(num) }; - - // 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); + let num = match d128_from_str(s.as_str().as_ref()) { + Ok(num) => num, + Err(s) => { + cx.span_err(lit.span, s); + return DummyResult::any(sp); + } + }; + let num = unsafe { ::std::mem::transmute::(num) }; + + // 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()) + } } - // This should never happen. - cx.span_err(sp, "not a valid d128 number"); - DummyResult::any(sp) -} -#[plugin_registrar] -pub fn plugin_registrar(reg: &mut Registry) { - reg.register_macro("d128", d128_lit) } -fn 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); + } + let num = match d64_from_str(s.as_str().as_ref()) { + Ok(num) => num, + Err(s) => { + cx.span_err(lit.span, s); + return DummyResult::any(sp); + } + }; + let num = unsafe { ::std::mem::transmute::(num) }; + + // 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/src/dec128.rs b/src/dec128.rs index 028b5f41..46e01014 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1105,6 +1105,45 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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(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::() + }); + } + #[test] fn it_parses_zero_in_exp_notation() { assert_eq!(d128::from_str("0E-8").unwrap(), d128!(0.00000000)); diff --git a/src/dec64.rs b/src/dec64.rs new file mode 100644 index 00000000..06eff30a --- /dev/null +++ b/src/dec64.rs @@ -0,0 +1,1361 @@ +use super::Class; +use super::Status; +use super::Rounding; + +use 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; +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::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, Deref}; +use std::str::FromStr; +use std::str::from_utf8_unchecked; + +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 = 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: d64 = uninitialized(); + *decDoubleFromInt32(&mut res, 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: d64 = uninitialized(); + *decDoubleFromUInt32(&mut res, val) + } + } +} + +// /// Converts an u64 to d64. The result is exact and no error is possible. +// impl From for d64 { +// fn from(mut val: u64) -> d64 { +// let mut bcd = [0; 34]; +// let mut i = 33; +// while val > 0 { +// bcd[i] = (val % 10) as u8; +// val /= 10; +// i -= 1; +// } +// unsafe { +// let mut res: d64 = uninitialized(); +// *decDoubleFromBCD(&mut res, 0, bcd.as_ptr(), 0) +// } +// } +// } + +// /// 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 mut res: d64; + unsafe { + res = uninitialized(); + decDoubleFromString(&mut res, cstr.as_ptr(), ctx); + } + 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() { + 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: $t = uninitialized(); *$ffi(&mut res, 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: $t = uninitialized(); *$ffi(&mut res, 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: d64 = uninitialized(); + *decDoubleShift(&mut res, 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: d64 = uninitialized(); + *decDoubleShift(&mut res, 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: Context = uninitialized(); + *decContextDefault(&mut res, 64) + } + } + + /// Initialize a `Context` with the specified `Rounding`. + fn with_rounding(rounding: Rounding) -> Context { + unsafe { + let mut res: Context = uninitialized(); + let mut ctx = *decContextDefault(&mut res, 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 + } + + /// 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() != 32 { + Self::from_str("qNaN").unwrap() + } else { + unsafe { + let mut res: d64 = uninitialized(); + for (i, octet) in s.as_bytes().chunks(2).rev().enumerate() { + res.bytes[i] = match u8::from_str_radix(from_utf8_unchecked(octet), 8) { + 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 = uninitialized(); + *decDoubleZero(&mut res) + } + } + + /// Returns the d64 representing +Infinity. + pub fn infinity() -> d64 { + d64!(Infinity) + } + + /// Returns the d64 representing -Infinity. + pub fn neg_infinity() -> d64 { + d64!(-Infinity) + } + + // 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 = uninitialized(); + let mut num_exp: decNumber = uninitialized(); + 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 = uninitialized(); + let mut num_exp: decNumber = uninitialized(); + 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 = uninitialized(); + 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 = uninitialized(); + 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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 + /// + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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: d64 = uninitialized(); + *decDoubleCompare(&mut res, 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: d64 = uninitialized(); + *decDoubleCompareTotal(&mut res, 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() + } + } +} + +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 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; +} + +#[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}; + + #[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()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8ec1dc94..529f6037 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,10 +37,32 @@ macro_rules! d128 { }} } +#[macro_export] +/// A macro to construct d64 literals. +/// +/// # Examples: +/// ``` +/// # #[macro_use] +/// # extern crate decimal; +/// # fn main() { +/// assert!(d64!(NaN).is_nan()); +/// assert!(d64!(0).is_zero()); +/// assert!(d64!(-0.1).is_negative()); +/// # } +/// ``` +macro_rules! d64 { + ($lit:expr) => {{ + use std::str::FromStr; + $crate::d64::from_str(stringify!($lit)).expect("Invalid decimal float literal") + }} +} + mod context; mod dec128; +mod dec64; pub use dec128::d128; +pub use dec64::d64; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] From c981d292824e4cc83e7ac132f8643f90a97daa70 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 03:01:16 -0500 Subject: [PATCH 18/75] adds simd benches --- Cargo.toml | 2 ++ src/dec128.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 74 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1b0099a4..8614ee72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,11 @@ ord_subset = { optional = true, version = "3" } rustc-serialize = { optional = true, version = "0.3" } serde = { optional = true, version = "1" } clap = { features = [], version = "2" } +faster = { version = "0.4", optional = true } [features] default = ["ord_subset", "serde"] +simd-benchmarks = ["faster"] [build-dependencies] cc = "1" diff --git a/src/dec128.rs b/src/dec128.rs index 46e01014..176112fd 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1105,6 +1105,76 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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 = "simd-benchmarks")] + #[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() + .simd_reduce(u64s(0), u64s(0), |acc, v| acc + v) + .sum() as f64 / 1e8) + - + ys.iter().sum::() + < + 1e-8 + ); + + b.iter(|| { + ((&xs[..]).simd_iter() + .simd_reduce(u64s(0), u64s(0), |acc, v| acc + v) + .sum() as f64 / 1e8) + }); + } + + #[cfg(feature = "simd-benchmarks")] + #[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() + .simd_reduce(f32s(0.0), f32s(0.0), |acc, v| acc + v) + .sum() + }); + } + #[bench] fn sums_vec_of_100_000_f32(b: &mut Bencher) { let x = 0.00012345f32; diff --git a/src/lib.rs b/src/lib.rs index 529f6037..13f06c11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,8 @@ extern crate serde_json; extern crate rand; #[cfg(test)] extern crate test; +#[cfg(feature = "simd-benchmarks")] +extern crate faster; #[macro_export] /// A macro to construct d128 literals. From 31221e880848d29eb401c273e8401e160f3917a7 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 03:02:43 -0500 Subject: [PATCH 19/75] bumps version to v2.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8614ee72..30ce7de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.0.4" +version = "2.1.0" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 2ca9d3f755df3ff960867886246eee0b9a7375f1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 20:43:02 -0500 Subject: [PATCH 20/75] changes some Into impls to From impls --- src/dec128.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 176112fd..f969402a 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -62,9 +62,9 @@ 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) } } @@ -215,16 +215,16 @@ impl FromStr for d128 { } /// 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) }) } } From d5878ab44a9aed9bd920a431f3bf6e90f1da1e4c Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 20:43:16 -0500 Subject: [PATCH 21/75] impl From for u64 --- src/dec128.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index f969402a..31d3b092 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -147,6 +147,47 @@ impl From for d128 { } } +/// # Examples +/// ``` +/// #[macro_use] +/// extern crate decimal; +/// +/// 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] = unsafe { uninitialized() }; + let mut exp: i32 = unsafe { uninitialized() }; + unsafe { + let _ = decQuadToBCD(&val, &mut exp, &mut bcd); + //debug_assert_eq!(i, 0); + } + let mut u: u64 = 0; + let mut i: usize = 33; + let n: usize = val.digits() as usize; + assert!(n < 21); + while i > 33 - n { + u += bcd[i] as u64 * 10u64.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); + // } + u + // 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 { @@ -799,6 +840,18 @@ 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 + /// ``` + /// #[macro_use] + /// extern crate decimal; + /// + /// 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) }) } @@ -958,6 +1011,7 @@ extern "C" { 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; @@ -1105,6 +1159,29 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[test] + fn verifies_u64_from_d128_on_large_number_of_examples() { + macro_rules! check { + ($n:expr) => { + assert_eq!(u64::from(d128!($n)), $n); + } + } + + check!(0); + check!(1); + check!(2); + check!(3); + check!(4); + check!(10); + check!(100); + check!(1456789); + 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; From 262b34d417c1e90456b658a320cd1e75df2d2ca7 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 30 Jan 2018 20:43:48 -0500 Subject: [PATCH 22/75] version bump to v2.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 30ce7de7..054ec863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.0" +version = "2.1.1" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From cf022aa0655e62a84a1abd9de7a13b7c9d878c59 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 31 Jan 2018 04:58:04 -0500 Subject: [PATCH 23/75] refinements to from for u64 --- src/dec128.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 31d3b092..7184095f 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -179,7 +179,21 @@ impl From for u64 { // for (i, b) in bcd.iter().rev().enumerate().take(val.digits() as usize) { // u += (*b as u64) * 10u64.pow(i as u32); // } - u + //println!("exp = {}", exp); + match exp { + e if e > 0 => u * 10u64.pow(exp as u32), + + e if e < 0 => u / 10u64.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() @@ -1159,11 +1173,24 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + + #[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) => { - assert_eq!(u64::from(d128!($n)), $n); + let u: u64 = $n; + assert_eq!(u64::from(d128!($n)), u); } } @@ -1175,6 +1202,7 @@ mod tests { check!(10); check!(100); check!(1456789); + check!(12345678); check!(123456789); check!(17473551615); check!(1744073551615); From 9eaa7274127870a40ad005d3dba24b5d29607343 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 31 Jan 2018 18:58:27 -0500 Subject: [PATCH 24/75] impl From for d128 --- src/dec128.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 2 +- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 7184095f..8b133a4e 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -168,12 +168,12 @@ impl From for u64 { let _ = decQuadToBCD(&val, &mut exp, &mut bcd); //debug_assert_eq!(i, 0); } - let mut u: u64 = 0; + let mut u: u128 = 0; let mut i: usize = 33; let n: usize = val.digits() as usize; - assert!(n < 21); + //assert!(n < 21, "val.digits() = {} (> 21); val = {}", n, val); while i > 33 - n { - u += bcd[i] as u64 * 10u64.pow((33 - i) as u32); + 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) { @@ -181,11 +181,11 @@ impl From for u64 { // } //println!("exp = {}", exp); match exp { - e if e > 0 => u * 10u64.pow(exp as u32), + e if e > 0 => (u * 10u128.pow(exp as u32)) as u64, - e if e < 0 => u / 10u64.pow(exp.abs() as u32), + e if e < 0 => (u / 10u128.pow(exp.abs() as u32)) as u64, - _ => u + _ => u as u64 } // if exp > 0 { // u *= 10u64.pow(exp as u32); @@ -219,6 +219,57 @@ impl From for d128 { } } +/// Converts an u128 to d128. The result is exact and no error is possible. +/// +/// # Examples +/// ``` +/// #![feature(i128, i128_type)] +/// #[macro_use] +/// extern crate decimal; +/// +/// 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: d128 = uninitialized(); + *decQuadFromBCD(&mut res, 0, bcd.as_ptr(), 0) + } + } +} + /// Converts an i64 to d128. The result is exact and no error is possible. impl From for d128 { fn from(val: i64) -> d128 { @@ -1173,6 +1224,13 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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 checks_a_u64_potential_edge_case() { diff --git a/src/lib.rs b/src/lib.rs index 13f06c11..df9edc3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(const_fn, test)] +#![feature(const_fn, test, i128_type)] #[macro_use] extern crate bitflags; From 4e7e9cc1dc9b232c46e2968372a9b27fb3a75e6a Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 1 Feb 2018 03:54:02 -0500 Subject: [PATCH 25/75] quiets a couple warnings --- decimal-macros/src/lib.rs | 1 + src/dec64.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index ff130329..138f1685 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -8,6 +8,7 @@ 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::codemap::Span; diff --git a/src/dec64.rs b/src/dec64.rs index 06eff30a..5f6c44cc 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -957,7 +957,7 @@ extern "C" { 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 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; From 1b2409b741cb454dbb74c32c0388e33c3083f051 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 28 Feb 2018 21:41:54 -0500 Subject: [PATCH 26/75] Adds (optional) impl of slog::Value for d128, d64 This allows d128 to be used as a key-value pair via the slog logging macros, e.g. ``` let x = d128::zero(); info!(logger, "something important"; "x" => x); ``` --- Cargo.toml | 3 ++- src/dec128.rs | 15 +++++++++++++++ src/dec64.rs | 15 +++++++++++++++ src/lib.rs | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 054ec863..59ed21e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,10 @@ rustc-serialize = { optional = true, version = "0.3" } serde = { optional = true, version = "1" } clap = { features = [], version = "2" } faster = { version = "0.4", optional = true } +slog = { version = "2", optional = true } [features] -default = ["ord_subset", "serde"] +default = ["ord_subset", "serde", "slog"] simd-benchmarks = ["faster"] [build-dependencies] diff --git a/src/dec128.rs b/src/dec128.rs index 8b133a4e..ccb801b2 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -10,6 +10,8 @@ use ord_subset; 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; @@ -1070,6 +1072,18 @@ impl d128 { } } +#[cfg(feature = "slog")] +impl slog::Value for d128 { + fn serialize( + &self, + _record: &slog::Record, + key: slog::Key, + serializer: &mut 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; @@ -1202,6 +1216,7 @@ extern "C" { -> *mut decNumber; } +#[allow(unused)] #[cfg(test)] mod tests { #[cfg(any(feature = "ord_subset", feature = "rustc-serialize"))] diff --git a/src/dec64.rs b/src/dec64.rs index 5f6c44cc..b11122db 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -10,6 +10,8 @@ use ord_subset; 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; @@ -952,6 +954,18 @@ impl d64 { } } +#[cfg(feature = "slog")] +impl slog::Value for d64 { + fn serialize( + &self, + _record: &slog::Record, + key: slog::Key, + serializer: &mut 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; @@ -1083,6 +1097,7 @@ extern "C" { -> *mut decNumber; } +#[allow(unused)] #[cfg(test)] mod tests { #[cfg(any(feature = "ord_subset", feature = "rustc-serialize"))] diff --git a/src/lib.rs b/src/lib.rs index df9edc3c..822945d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ extern crate rand; extern crate test; #[cfg(feature = "simd-benchmarks")] extern crate faster; +#[cfg(feature = "slog")] +extern crate slog; #[macro_export] /// A macro to construct d128 literals. From d05a254b3d5351411708ab810d9653272082b87f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 28 Feb 2018 21:42:35 -0500 Subject: [PATCH 27/75] version bump to v2.1.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 59ed21e7..e653a74e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.1" +version = "2.1.2" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 7b7f887fe5a75d02c36d8d79953b0c53f0e7d4bd Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 9 Mar 2018 02:55:02 -0500 Subject: [PATCH 28/75] `impl From for d64` --- src/dec64.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/dec64.rs b/src/dec64.rs index b11122db..4d757bd4 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -26,6 +26,7 @@ use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, R ShlAssign, Shr, ShrAssign, Deref}; use std::str::FromStr; use std::str::from_utf8_unchecked; +use d128; thread_local!(static CTX: RefCell = RefCell::new(d64::default_context())); thread_local!(static ROUND_DOWN: RefCell = RefCell::new(d64::with_rounding(Rounding::Down))); @@ -966,6 +967,15 @@ impl slog::Value for d64 { } } +impl From for d64 { + fn from(val: d128) -> Self { + d64::with_context(|ctx| unsafe { + let mut res: d64 = uninitialized(); + *decDoubleFromWider(&mut res, &val, ctx) + }) + } +} + extern "C" { // Context. fn decContextDefault(ctx: *mut Context, kind: uint32_t) -> *mut Context; @@ -978,6 +988,7 @@ extern "C" { 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. @@ -1120,6 +1131,12 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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); From 4dc16bfd4e55018e23e76eed4010c4fd80435358 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 9 Mar 2018 02:55:50 -0500 Subject: [PATCH 29/75] version bump to v2.1.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e653a74e..779b172a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.2" +version = "2.1.3" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 015d6762e36b2decea454dda4751ca4d646504e7 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 28 Mar 2018 17:37:16 -0400 Subject: [PATCH 30/75] `From for f64`, `From for f32` --- Cargo.toml | 1 + src/dec128.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +++ 3 files changed, 63 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 779b172a..b03c8240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ serde = { optional = true, version = "1" } clap = { features = [], version = "2" } faster = { version = "0.4", optional = true } slog = { version = "2", optional = true } +approx = "0.1" [features] default = ["ord_subset", "serde", "slog"] diff --git a/src/dec128.rs b/src/dec128.rs index ccb801b2..8f4582ef 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -131,6 +131,7 @@ 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(); @@ -149,6 +150,24 @@ impl From for d128 { } } +impl From for f64 { + #[inline] + fn from(x: d128) -> Self { + f64::from_str(&(format!("{}", x))).expect("f64::from_str conversion from d128") + } +} + +impl From for f32 { + #[inline] + fn from(x: d128) -> Self { + //f32::from_str(&(format!("{}", x))).expect("f32::from_str conversion from d128") + + // for whatever reason, the f32::from_str is slow + f64::from_str(&(format!("{}", x))).expect("f64::from_str conversion from d128") + as f32 + } +} + /// # Examples /// ``` /// #[macro_use] @@ -1239,6 +1258,46 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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); + } + + #[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)); + } + #[test] fn checks_a_u64_conversion_that_failed_somehow() { let e: d128 = d128!(100000000); diff --git a/src/lib.rs b/src/lib.rs index 822945d1..2a36baa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,9 @@ extern crate test; extern crate faster; #[cfg(feature = "slog")] extern crate slog; +#[cfg(test)] +#[macro_use] +extern crate approx; #[macro_export] /// A macro to construct d128 literals. From 950180a8ef46e9863c7f3db6caeba21637448a88 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 29 Mar 2018 00:19:44 -0400 Subject: [PATCH 31/75] removes now-stable i128_type from features --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2a36baa8..14c08a53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(const_fn, test, i128_type)] +#![feature(const_fn, test)] #[macro_use] extern crate bitflags; From 7bc83bbe8a07ec445f2b2da586b7895d90dd0d6f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 30 Mar 2018 04:51:33 -0400 Subject: [PATCH 32/75] safer f64, f32 conversion impls --- src/dec128.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 8f4582ef..f05db876 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -26,6 +26,7 @@ use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, R 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))); @@ -153,18 +154,43 @@ impl From for d128 { impl From for f64 { #[inline] fn from(x: d128) -> Self { - f64::from_str(&(format!("{}", x))).expect("f64::from_str conversion from d128") + 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 { - //f32::from_str(&(format!("{}", x))).expect("f32::from_str conversion from d128") - - // for whatever reason, the f32::from_str is slow - f64::from_str(&(format!("{}", x))).expect("f64::from_str conversion from d128") - as f32 + 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))) + //.expect(&format!("f64::from_str conversion from d128: {}", x)) + .unwrap_or(f64::NAN) + as f32 + } } } @@ -658,7 +684,7 @@ impl d128 { { CTX.with(|ctx| f(&mut ctx.borrow_mut())) } - + /// Creates a d128 from raw bytes. Endianess is host dependent. pub const fn from_raw_bytes(bytes: [u8; 16]) -> d128 { d128 { bytes } @@ -1258,6 +1284,15 @@ mod tests { #[allow(unused_imports)] use test::{black_box, Bencher}; + #[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; From 5694efb7105c75c8516738a7ffc0eac1a02f183f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 16 May 2018 22:26:33 -0400 Subject: [PATCH 33/75] switches faster dep to point to git master; drops "simd-benchmarks" feat for crate name feat also fixes faster benches broken by upstream changes --- Cargo.toml | 3 +-- src/dec128.rs | 16 ++++++++-------- src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b03c8240..ba2e377d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,12 @@ ord_subset = { optional = true, version = "3" } rustc-serialize = { optional = true, version = "0.3" } serde = { optional = true, version = "1" } clap = { features = [], version = "2" } -faster = { version = "0.4", optional = true } +faster = { git = "https://github.com/AdamNiederer/faster", branch = "master", optional = true } slog = { version = "2", optional = true } approx = "0.1" [features] default = ["ord_subset", "serde", "slog"] -simd-benchmarks = ["faster"] [build-dependencies] cc = "1" diff --git a/src/dec128.rs b/src/dec128.rs index f05db876..f315e86e 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1401,7 +1401,7 @@ mod tests { } - #[cfg(feature = "simd-benchmarks")] + #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_u64_1e8_of_float_simd(b: &mut Bencher) { use faster::*; @@ -1414,8 +1414,8 @@ mod tests { } assert!( - ((&xs[..]).simd_iter() - .simd_reduce(u64s(0), u64s(0), |acc, v| acc + v) + ((&xs[..]).simd_iter(u64s(0)) + .simd_reduce(u64s(0), |acc, v| acc + v) .sum() as f64 / 1e8) - ys.iter().sum::() @@ -1424,13 +1424,13 @@ mod tests { ); b.iter(|| { - ((&xs[..]).simd_iter() - .simd_reduce(u64s(0), u64s(0), |acc, v| acc + v) + ((&xs[..]).simd_iter(u64s(0)) + .simd_reduce(u64s(0), |acc, v| acc + v) .sum() as f64 / 1e8) }); } - #[cfg(feature = "simd-benchmarks")] + #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_f32_simd(b: &mut Bencher) { use faster::*; @@ -1441,8 +1441,8 @@ mod tests { } b.iter(|| { - (&xs[..]).simd_iter() - .simd_reduce(f32s(0.0), f32s(0.0), |acc, v| acc + v) + (&xs[..]).simd_iter(f32s(0.0)) + .simd_reduce(f32s(0.0), |acc, v| acc + v) .sum() }); } diff --git a/src/lib.rs b/src/lib.rs index 14c08a53..b2fed44f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ extern crate serde_json; extern crate rand; #[cfg(test)] extern crate test; -#[cfg(feature = "simd-benchmarks")] +#[cfg(feature = "faster")] extern crate faster; #[cfg(feature = "slog")] extern crate slog; From d4b24469e6e182842a9f1fd83a7623d278db033e Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 16 May 2018 22:28:30 -0400 Subject: [PATCH 34/75] sets autobins=false to silence cargo warning about /bin --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ba2e377d..60711cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/alkis/decimal" documentation = "https://alkis.github.io/decimal" keywords = ["decimal", "decNumber"] license = "Apache-2.0" +autobins = false [lib] name = "decimal" From 921fb238492dc20ee1f2077551cae60ce48474e1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 16 May 2018 22:29:11 -0400 Subject: [PATCH 35/75] adds d128::from_f32_lossy(f32) -> d128, d128::from_f64_lossy(f64) -> d128 + tests and benches --- src/dec128.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/dec128.rs b/src/dec128.rs index f315e86e..62361865 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -695,6 +695,26 @@ impl d128 { self.bytes } + /// 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. + #[inline] + pub fn from_f64_lossy(x: f64) -> d128 { + d128::from((x * 1e8) as i64) * d128!(1e-8) + } + + /// 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. + #[inline] + pub fn from_f32_lossy(x: f32) -> d128 { + d128::from((x * 1e8) as i64) * d128!(1e-8) + } + /// Returns the thread local status. pub fn get_status() -> Status { d128::with_context(|ctx| Status::from_bits_truncate(ctx.status)) @@ -1474,7 +1494,7 @@ mod tests { } #[bench] - fn sums_vec_of_100_000(b: &mut Bencher) { + 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 { @@ -1486,6 +1506,102 @@ mod tests { }); } + #[bench] + fn d128_to_u64(b: &mut Bencher) { + let x = d128!(12345); + b.iter(|| u64::from(x)); + } + + #[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)); From 2127b9ba2a135377861e485bb57bf39215e10eb9 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 16 May 2018 22:29:21 -0400 Subject: [PATCH 36/75] version bump to v2.1.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 60711cfa..ab34f98a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.3" +version = "2.1.4" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 3ddad7aa63d86cf44b2fc42d2646154f44a2a884 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 25 May 2018 20:51:36 -0400 Subject: [PATCH 37/75] minor cruft removal --- src/dec128.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 62361865..cb405edb 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -186,8 +186,7 @@ impl From for f32 { } } else { // for whatever reason, the f32::from_str is slow - f64::from_str(&(format!("{}", x))) - //.expect(&format!("f64::from_str conversion from d128: {}", x)) + f64::from_str(&format!("{}", x)) .unwrap_or(f64::NAN) as f32 } From ae73a7645b1b8ce25537b586ed35c630c9ee275d Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sat, 26 May 2018 01:15:32 -0400 Subject: [PATCH 38/75] underscore handling for d128! and d64! --- Cargo.toml | 3 +++ decimal-macros/src/lib.rs | 15 +++++++++++---- src/lib.rs | 14 +++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab34f98a..82bc3fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,6 @@ cc = "1" [dev-dependencies] serde_json = "1" rand = "0.4" + +[profile.test] +opt-level = 2 diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index 138f1685..65ede3a3 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -26,8 +26,6 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_macro("d128", self::d128_lit::d128_lit); } - - mod d128_lit { use super::*; @@ -53,13 +51,18 @@ mod d128_lit { cx.span_err(sp, "one argument needed"); return DummyResult::any(sp); } - let num = match d128_from_str(s.as_str().as_ref()) { + + // 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 = unsafe { ::std::mem::transmute::(num) }; // Create array literal @@ -130,7 +133,11 @@ mod d64_lit { cx.span_err(sp, "one argument needed"); return DummyResult::any(sp); } - let num = match d64_from_str(s.as_str().as_ref()) { + + // 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); diff --git a/src/lib.rs b/src/lib.rs index b2fed44f..22a389bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,16 +31,23 @@ extern crate approx; /// ``` /// # #[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; - $crate::d128::from_str(stringify!($lit)).expect("Invalid decimal float literal") + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d128::from_str(&clean).expect("Invalid decimal float literal") }} } @@ -51,10 +58,15 @@ macro_rules! d128 { /// ``` /// # #[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 { From 7365ac8abd32f3bffe5d6522e8ad08e9433896e1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sat, 26 May 2018 01:16:03 -0400 Subject: [PATCH 39/75] benchmarks for generating random d128 in range -1, 1 --- src/dec128.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index cb405edb..97585124 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1688,6 +1688,44 @@ mod tests { }); } + #[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 { From c999333c42b21e010490d244aad02a8bf7407abf Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sat, 26 May 2018 01:16:30 -0400 Subject: [PATCH 40/75] ver bump to v2.1.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 82bc3fb5..6d700434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.4" +version = "2.1.5" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 82c59f1ecf59f5deb1cb6d7e81ac3d3c3ff1d6b3 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 1 Jun 2018 14:47:37 -0400 Subject: [PATCH 41/75] reorg binary location and add to-string binary --- Cargo.toml | 10 +++++++++- src/{get_bytes.rs => bytes.rs} | 0 src/{bin/run-test.rs => run_test.rs} | 0 src/to_string.rs | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) rename src/{get_bytes.rs => bytes.rs} (100%) rename src/{bin/run-test.rs => run_test.rs} (100%) create mode 100644 src/to_string.rs diff --git a/Cargo.toml b/Cargo.toml index 6d700434..0759b1ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,17 @@ name = "decimal" path = "src/lib.rs" [[bin]] -path = "src/get_bytes.rs" +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" libc = "0.2" diff --git a/src/get_bytes.rs b/src/bytes.rs similarity index 100% rename from src/get_bytes.rs rename to src/bytes.rs diff --git a/src/bin/run-test.rs b/src/run_test.rs similarity index 100% rename from src/bin/run-test.rs rename to src/run_test.rs diff --git a/src/to_string.rs b/src/to_string.rs new file mode 100644 index 00000000..0f32a3a7 --- /dev/null +++ b/src/to_string.rs @@ -0,0 +1,17 @@ +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("dec_literal") + .help("Decimal float literal to show string for") + .required(true)) + .get_matches(); + + let literal = args.value_of("dec_literal").unwrap(); + + let d = decimal::d128::from_str(&literal).expect("Invalid float literal"); + println!("{}", d); +} From 2f243192ffb1caa3410c20dd3353f0d697875ef8 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Fri, 1 Jun 2018 18:08:18 -0400 Subject: [PATCH 42/75] documents a couple failed tries to remove exponential form --- decNumber/decCommon.c | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 '.' From b820d5e57b162d3d9dd952ef61b567526755e3c1 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sat, 23 Jun 2018 07:26:23 -0400 Subject: [PATCH 43/75] new bench for a scenario where you create a i64 key from a d128 price (400ns) --- src/dec128.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 97585124..76c61ff9 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1505,6 +1505,17 @@ mod tests { }); } + #[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); From 3da849b2bd6117674b27bf5a14849a7ae2f2f0bd Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sun, 24 Jun 2018 14:49:36 -0400 Subject: [PATCH 44/75] adds `as_bytes` alias to `to_raw_bytes` --- src/dec128.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 76c61ff9..8989eb06 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -694,6 +694,9 @@ impl d128 { self.bytes } + /// Returns `self.to_raw_bytes()`. I prefer this name. + pub fn as_bytes(&self) -> [u8; 16] { self.to_raw_bytes() } + /// 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. From 8fa94c0052820054eb717c8a62f3351112402500 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sun, 19 Aug 2018 21:52:18 -0400 Subject: [PATCH 45/75] removes trailing whitespace --- decimal-macros/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index 65ede3a3..37e07d32 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -33,7 +33,7 @@ mod d128_lit { // 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 => { @@ -42,7 +42,7 @@ mod d128_lit { return DummyResult::any(sp); } }; - + match &ex.node { &ExprKind::Lit(ref lit) => match &lit.node { &LitKind::Str(ref s, StrStyle::Cooked) => { @@ -64,7 +64,7 @@ mod d128_lit { }; let num = unsafe { ::std::mem::transmute::(num) }; - + // Create array literal let mut vec = Vec::new(); for i in 0..16 { @@ -73,7 +73,7 @@ mod d128_lit { 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); }, _ => {} @@ -90,7 +90,7 @@ mod d128_lit { 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); @@ -115,7 +115,7 @@ mod d64_lit { // 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 => { @@ -124,7 +124,7 @@ mod d64_lit { return DummyResult::any(sp); } }; - + match &ex.node { &ExprKind::Lit(ref lit) => match &lit.node { &LitKind::Str(ref s, StrStyle::Cooked) => { @@ -145,7 +145,7 @@ mod d64_lit { } }; let num = unsafe { ::std::mem::transmute::(num) }; - + // Create array literal let mut vec = Vec::new(); for i in 0..8 { @@ -154,7 +154,7 @@ mod d64_lit { 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); }, _ => {} @@ -171,7 +171,7 @@ mod d64_lit { 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); From 2c7b6a13b1d00f6addcd7aa20b71b5203e4c2a15 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 28 Aug 2018 18:53:08 -0400 Subject: [PATCH 46/75] adds d128::finite_or_err(self, err: E) -> Result --- src/dec128.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dec128.rs b/src/dec128.rs index 8989eb06..ca873234 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1137,6 +1137,14 @@ impl d128 { false => f() } } + + pub fn finite_or_err(self, err: E) -> Result { + if self.is_finite() { + Ok(self) + } else { + Err(err) + } + } } #[cfg(feature = "slog")] From 3633eab4699dcf82ac0caab2ae2331fb73d309ef Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 29 Aug 2018 03:01:15 -0400 Subject: [PATCH 47/75] spent a while on this and it is NASTY --- decimal-macros/src/lib.rs | 10 ++++++++-- decimal-macros/tests/test.rs | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs index 37e07d32..028409de 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -1,4 +1,8 @@ -#![feature(plugin_registrar, rustc_private)] +//#![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; @@ -11,6 +15,7 @@ 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; @@ -63,7 +68,8 @@ mod d128_lit { } }; - let num = unsafe { ::std::mem::transmute::(num) }; + //let num = unsafe { ::std::mem::transmute::(num) }; + let num = num.as_bytes(); // Create array literal let mut vec = Vec::new(); diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs index 504d725c..2b9a7c6d 100644 --- a/decimal-macros/tests/test.rs +++ b/decimal-macros/tests/test.rs @@ -1,4 +1,4 @@ -#![feature(plugin, const_fn)] +#![feature(plugin, const_fn, const_let)] #![plugin(decimal_macros)] extern crate decimal; @@ -28,7 +28,6 @@ fn zero_eq_zero() { #[test] fn create_d128_const() { - const ZERO: d128 = d128!(0); + static ZERO: d128 = { d128!(0) }; assert_eq!(ZERO, d128::zero()); } - From d3f6d918cd9e1369ace695e2a0da3072138b2d27 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 20 Sep 2018 04:39:34 -0400 Subject: [PATCH 48/75] From for u128 (and more benches) --- Cargo.toml | 2 +- src/dec128.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0759b1ba..818b8706 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.5" +version = "2.1.6" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" diff --git a/src/dec128.rs b/src/dec128.rs index ca873234..4af5cf85 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -193,6 +193,8 @@ impl From for f32 { } } +/// Create a `u64` from a `d128`, which is assumed to be > 0. +/// /// # Examples /// ``` /// #[macro_use] @@ -248,6 +250,80 @@ impl From for u64 { } } +/// Create a `u128` from a `d128`, which is assumed to be > 0. +/// +/// # Examples +/// ``` +/// #[macro_use] +/// extern crate decimal; +/// +/// 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!(1)); + 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] = unsafe { uninitialized() }; + let mut exp: i32 = unsafe { uninitialized() }; + 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 { @@ -1363,6 +1439,15 @@ mod tests { 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); @@ -1371,6 +1456,46 @@ mod tests { 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); @@ -1407,6 +1532,31 @@ mod tests { 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; @@ -1533,6 +1683,25 @@ mod tests { 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; From b02b59546d1753109d0a08c6d345c7d05dfaaea6 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 11:51:21 -0400 Subject: [PATCH 49/75] replaces d128! and d64! compiler plugins with proc macro replacements --- Cargo.toml | 3 +- decimal-macros/Cargo.toml | 6 +- decimal-macros/src/lib.rs | 230 ++++------------------- decimal-macros/src/plugin.rs | 195 +++++++++++++++++++ decimal-macros/tests/test.rs | 22 ++- decimal-macros/tests/without_const_fn.rs | 47 ----- src/dec128.rs | 47 +++-- src/dec64.rs | 37 +++- src/lib.rs | 6 +- 9 files changed, 328 insertions(+), 265 deletions(-) create mode 100644 decimal-macros/src/plugin.rs delete mode 100644 decimal-macros/tests/without_const_fn.rs diff --git a/Cargo.toml b/Cargo.toml index 0759b1ba..b581fa85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.5" +version = "2.1.6" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" @@ -46,6 +46,7 @@ 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/decimal-macros/Cargo.toml b/decimal-macros/Cargo.toml index cf790dc1..c3c185e8 100644 --- a/decimal-macros/Cargo.toml +++ b/decimal-macros/Cargo.toml @@ -1,15 +1,17 @@ [package] name = "decimal-macros" -version = "0.1.0" +version = "0.2.0" authors = ["Callum Tolley "] [lib] name = "decimal_macros" -plugin = true +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 index 028409de..e771627b 100644 --- a/decimal-macros/src/lib.rs +++ b/decimal-macros/src/lib.rs @@ -1,195 +1,43 @@ -//#![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::*; - -#[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 = unsafe { ::std::mem::transmute::(num) }; - 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()) - } - } - +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() } -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 = unsafe { ::std::mem::transmute::(num) }; - - // 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()) - } - } +#[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..0bd1177d --- /dev/null +++ b/decimal-macros/src/plugin.rs @@ -0,0 +1,195 @@ +//#![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::*; + +#[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 = unsafe { ::std::mem::transmute::(num) }; + 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 = unsafe { ::std::mem::transmute::(num) }; + + // 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 index 2b9a7c6d..1075552f 100644 --- a/decimal-macros/tests/test.rs +++ b/decimal-macros/tests/test.rs @@ -1,8 +1,18 @@ -#![feature(plugin, const_fn, const_let)] -#![plugin(decimal_macros)] +#![feature(proc_macro_non_items)] +#![feature(const_fn)] +#![feature(const_let)] + +extern crate decimal_macros; extern crate decimal; use decimal::d128; +use decimal_macros::*; + +fn f(x: i32) -> i32 { x * 2 } + +macro_rules! f { + ($x:expr) => { x * 2 } +} #[test] fn basic_plugin_sanity_checks() { @@ -18,16 +28,16 @@ fn basic_plugin_sanity_checks() { } assert!(eq); - assert_eq!(d128!(0.1), d128::from(1) / d128::from(10)); + assert_eq!(d128!(0.1), decimal::d128::from(1) / decimal::d128::from(10)); } #[test] fn zero_eq_zero() { - assert_eq!(d128!(0), d128::zero()); + assert_eq!(d128!(0), decimal::d128::zero()); } #[test] fn create_d128_const() { - static ZERO: d128 = { d128!(0) }; - assert_eq!(ZERO, d128::zero()); + const ZERO: decimal::d128 = { d128!(0) }; + assert_eq!(ZERO, decimal::d128::zero()); } diff --git a/decimal-macros/tests/without_const_fn.rs b/decimal-macros/tests/without_const_fn.rs deleted file mode 100644 index b61f5117..00000000 --- a/decimal-macros/tests/without_const_fn.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![feature(plugin)] -#![plugin(decimal_macros)] -extern crate decimal; - -use decimal::d128; -use std::str::FromStr; - -#[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), d128::from(1) / d128::from(10)); -} - -#[test] -fn zero_eq_zero() { - assert_eq!(d128!(0), d128::zero()); -} - -#[test] -fn it_parses_a_negative_number() { - assert_eq!(d128!(-1), d128::from_str("-1").unwrap()); - assert_eq!(d128!(-0.1), d128::from_str("-0.1").unwrap()); -} - -#[test] -fn it_does_not_parse_a_negative_number_but_we_check_something_else() { - assert_eq!(-d128!(1), d128::from_str("-1").unwrap()); - assert_eq!(-d128!(0.1), d128::from_str("-0.1").unwrap()); -} - -#[test] -fn it_parses_nan() { - assert!(d128!(NaN).is_nan()); -} - - diff --git a/src/dec128.rs b/src/dec128.rs index ca873234..97fc4c85 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -195,8 +195,10 @@ impl From for f32 { /// # Examples /// ``` -/// #[macro_use] +/// #![feature(proc_macro_non_items)] /// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; /// /// fn main() { /// let x = d128!(12345); @@ -269,10 +271,10 @@ impl From for d128 { /// /// # Examples /// ``` -/// #![feature(i128, i128_type)] -/// #[macro_use] +/// #![feature(proc_macro_non_items)] /// extern crate decimal; -/// +/// extern crate decimal_macros; +/// use decimal_macros::*; /// use decimal::d128; /// /// fn main() { @@ -702,9 +704,10 @@ impl d128 { /// 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. + #[deprecated(since="2.1.6")] #[inline] pub fn from_f64_lossy(x: f64) -> d128 { - d128::from((x * 1e8) as i64) * d128!(1e-8) + d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() } /// Multiplies `x` by `1e8`, truncates to integer, converts to `d128`, then @@ -712,9 +715,10 @@ impl d128 { /// 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. + #[deprecated(since="2.1.6")] #[inline] pub fn from_f32_lossy(x: f32) -> d128 { - d128::from((x * 1e8) as i64) * d128!(1e-8) + d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() } /// Returns the thread local status. @@ -757,13 +761,15 @@ impl d128 { } /// Returns the d128 representing +Infinity. + #[deprecated(since="2.1.6")] pub fn infinity() -> d128 { - d128!(Infinity) + d128::from_str("Infinity").unwrap() } /// Returns the d128 representing -Infinity. + #[deprecated(since="2.1.6")] pub fn neg_infinity() -> d128 { - d128!(-Infinity) + d128::from_str("-Infinity").unwrap() } // Computational. @@ -905,8 +911,10 @@ impl d128 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d128!(0.1); @@ -925,8 +933,10 @@ impl d128 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d128!(0.1); @@ -944,8 +954,10 @@ impl d128 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d128!(0.1); @@ -977,8 +989,10 @@ impl d128 { /// /// # Examples /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let x = d128!(1.2345); @@ -1314,6 +1328,15 @@ mod tests { #[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()); diff --git a/src/dec64.rs b/src/dec64.rs index 4d757bd4..cb247f66 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -32,6 +32,7 @@ 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. @@ -545,6 +546,10 @@ impl d64 { 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)) @@ -586,12 +591,12 @@ impl d64 { /// Returns the d64 representing +Infinity. pub fn infinity() -> d64 { - d64!(Infinity) + d64::from_str("Infinity").unwrap() } /// Returns the d64 representing -Infinity. pub fn neg_infinity() -> d64 { - d64!(-Infinity) + d64::from_str("-Infinity").unwrap() } // Computational. @@ -733,8 +738,10 @@ impl d64 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d64!(0.1); @@ -753,8 +760,10 @@ impl d64 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d64!(0.1); @@ -772,8 +781,10 @@ impl d64 { /// # Examples /// /// ``` - /// #[macro_use] + /// #![feature(proc_macro_non_items)] /// extern crate decimal; + /// extern crate decimal_macros; + /// use decimal_macros::*; /// /// fn main() { /// let prec = d64!(0.1); @@ -1131,6 +1142,22 @@ mod tests { #[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; + $crate::d64::from_str(stringify!($lit)).expect("Invalid decimal float literal") + }} + } + #[test] fn from_d128() { assert_eq!(d64::from(d128!(1)), d64!(1)); diff --git a/src/lib.rs b/src/lib.rs index 22a389bf..694ed09a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ extern crate slog; #[macro_use] extern crate approx; +/* #[macro_export] /// A macro to construct d128 literals. /// @@ -75,6 +76,7 @@ macro_rules! d64 { $crate::d64::from_str(stringify!($lit)).expect("Invalid decimal float literal") }} } +*/ mod context; mod dec128; @@ -123,8 +125,10 @@ bitflags! { /// # Examples /// /// ``` - /// # #[macro_use] + /// # #![feature(proc_macro_non_items)] + /// # extern crate decimal_macros; /// # extern crate decimal; + /// # use decimal_macros::*; /// # use decimal::d128; /// # use decimal::Status; /// # fn main() { From 68e521d727347682f2acceb1619617bd3758ce9a Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 12:00:30 -0400 Subject: [PATCH 50/75] for posterity --- decimal-macros/src/plugin.rs | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/decimal-macros/src/plugin.rs b/decimal-macros/src/plugin.rs index 0bd1177d..94d2e381 100644 --- a/decimal-macros/src/plugin.rs +++ b/decimal-macros/src/plugin.rs @@ -25,6 +25,63 @@ 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); From 22384c261739433845f8ec9fad8afc9d5e099536 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 12:00:42 -0400 Subject: [PATCH 51/75] fixes doctest to use decimal_macros d128! --- src/dec128.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index a7724a9c..1e16e468 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -256,8 +256,10 @@ impl From for u64 { /// /// # Examples /// ``` -/// #[macro_use] +/// #![feature(proc_macro_non_items)] /// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; /// /// fn main() { /// let x = d128!(12345); @@ -271,7 +273,7 @@ impl From for u128 { 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!(1)); + debug_assert_eq!(ONE, d128::from_str("1").unwrap()); match val.digits() { x @ 0 ... 33 => { n = x as usize; From 9fb7905f03062c4682802a0a4f804d15ef9ce5a3 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 12:00:51 -0400 Subject: [PATCH 52/75] bump ver to v2.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b581fa85..dd294366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.1.6" +version = "2.2.0" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 6386e36c142433aa18f894205ac3ef109e3a5691 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 15:36:09 -0400 Subject: [PATCH 53/75] remove the old d128! and d64! macros --- src/lib.rs | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 694ed09a..43323782 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,60 +24,6 @@ extern crate slog; #[macro_use] extern crate approx; -/* -#[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") - }} -} -*/ - mod context; mod dec128; mod dec64; From d58251727d9972c5d31e9cdf7d86c31f307d6373 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Oct 2018 15:37:28 -0400 Subject: [PATCH 54/75] itty bitty stuff --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 43323782..f7805286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -#![feature(const_fn, test)] +#![feature(const_fn)] +#![cfg_attr(test, feature(test))] #[macro_use] extern crate bitflags; From 359b987a77613b097688b7ce00e6374b0518eb39 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 8 Oct 2018 17:01:45 -0400 Subject: [PATCH 55/75] proc_macro_hygiene switcharoo --- decimal-macros/tests/test.rs | 2 +- src/dec128.rs | 14 +++++++------- src/dec64.rs | 6 +++--- src/lib.rs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs index 1075552f..3519dd46 100644 --- a/decimal-macros/tests/test.rs +++ b/decimal-macros/tests/test.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro_non_items)] +#![feature(proc_macro_hygiene)] #![feature(const_fn)] #![feature(const_let)] diff --git a/src/dec128.rs b/src/dec128.rs index 1e16e468..c8701854 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -197,7 +197,7 @@ impl From for f32 { /// /// # Examples /// ``` -/// #![feature(proc_macro_non_items)] +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -256,7 +256,7 @@ impl From for u64 { /// /// # Examples /// ``` -/// #![feature(proc_macro_non_items)] +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -349,7 +349,7 @@ impl From for d128 { /// /// # Examples /// ``` -/// #![feature(proc_macro_non_items)] +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -989,7 +989,7 @@ impl d128 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -1011,7 +1011,7 @@ impl d128 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -1032,7 +1032,7 @@ impl d128 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -1067,7 +1067,7 @@ impl d128 { /// /// # Examples /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; diff --git a/src/dec64.rs b/src/dec64.rs index cb247f66..5df2852b 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -738,7 +738,7 @@ impl d64 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -760,7 +760,7 @@ impl d64 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; @@ -781,7 +781,7 @@ impl d64 { /// # Examples /// /// ``` - /// #![feature(proc_macro_non_items)] + /// #![feature(proc_macro_hygiene)] /// extern crate decimal; /// extern crate decimal_macros; /// use decimal_macros::*; diff --git a/src/lib.rs b/src/lib.rs index f7805286..69b7d21f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,7 +72,7 @@ bitflags! { /// # Examples /// /// ``` - /// # #![feature(proc_macro_non_items)] + /// # #![feature(proc_macro_hygiene)] /// # extern crate decimal_macros; /// # extern crate decimal; /// # use decimal_macros::*; From 19f32bfe976d184e058a777c623ae0221d9f73fa Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Wed, 24 Oct 2018 12:19:37 -0400 Subject: [PATCH 56/75] adds --round option to specify a precision --- src/to_string.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/to_string.rs b/src/to_string.rs index 0f32a3a7..2c039fd4 100644 --- a/src/to_string.rs +++ b/src/to_string.rs @@ -5,13 +5,26 @@ use std::str::FromStr; fn main() { let args: clap::ArgMatches = clap::App::new("to-string") .version("0.1") - .arg(clap::Arg::with_name("dec_literal") + .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("dec_literal").unwrap(); + 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); + } - let d = decimal::d128::from_str(&literal).expect("Invalid float literal"); println!("{}", d); } From fcbb11fdbc6baac33fec6c3f30923a7a1204197e Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 13 Dec 2018 14:38:39 -0500 Subject: [PATCH 57/75] add note to deprecation notice for _lossy f64/f32 fns --- src/dec128.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index c8701854..b93ba4af 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -782,7 +782,9 @@ impl d128 { /// 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. - #[deprecated(since="2.1.6")] + #[deprecated(since="2.1.6", note="`d128!(..)` macro now unavailable inside crate, \ + so this implementation is forced to use \ + `d128::from_str`, which is slow")] #[inline] pub fn from_f64_lossy(x: f64) -> d128 { d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() @@ -793,7 +795,9 @@ impl d128 { /// 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. - #[deprecated(since="2.1.6")] + #[deprecated(since="2.1.6", note="`d128!(..)` macro now unavailable inside crate, \ + so this implementation is forced to use \ + `d128::from_str`, which is slow")] #[inline] pub fn from_f32_lossy(x: f32) -> d128 { d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() From 6bbead10036eb0b8bce99c7ee37480905c3826b2 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 13 Dec 2018 14:50:38 -0500 Subject: [PATCH 58/75] perf(d128): faster from_f64_lossy/from_f32_lossy (and removed deprecation since it's fast now) --- src/dec128.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index b93ba4af..90168343 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -777,17 +777,26 @@ impl d128 { /// 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. - #[deprecated(since="2.1.6", note="`d128!(..)` macro now unavailable inside crate, \ - so this implementation is forced to use \ - `d128::from_str`, which is slow")] + #[cfg(target_endian = "little")] #[inline] pub fn from_f64_lossy(x: f64) -> d128 { - d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() + 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 @@ -795,12 +804,13 @@ impl d128 { /// 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. - #[deprecated(since="2.1.6", note="`d128!(..)` macro now unavailable inside crate, \ - so this implementation is forced to use \ - `d128::from_str`, which is slow")] + #[cfg(target_endian = "little")] #[inline] pub fn from_f32_lossy(x: f32) -> d128 { - d128::from((x * 1e8) as i64) * d128::from_str("1e-8").unwrap() + 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. @@ -1908,6 +1918,15 @@ mod tests { }); } + #[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(); From 65d62e6170eb58080b738022c6e8e285148d0342 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 13 Dec 2018 14:51:04 -0500 Subject: [PATCH 59/75] bump ver to v2.2.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dd294366..ef46ffd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.2.0" +version = "2.2.1" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From c80e87533b7041772a84ae6f390a87a01ed83e39 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 13 Dec 2018 14:56:59 -0500 Subject: [PATCH 60/75] style: minor changes from cargo fix --edition; upgrades to rust 2018 --- Cargo.toml | 3 ++- src/dec128.rs | 6 +++--- src/dec64.rs | 8 ++++---- src/lib.rs | 4 ++-- src/run_test.rs | 10 +++++----- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef46ffd6..e452b0dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.2.1" +version = "2.2.2" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" @@ -9,6 +9,7 @@ documentation = "https://alkis.github.io/decimal" keywords = ["decimal", "decNumber"] license = "Apache-2.0" autobins = false +edition = "2018" [lib] name = "decimal" diff --git a/src/dec128.rs b/src/dec128.rs index 90168343..0408932a 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; @@ -74,7 +74,7 @@ impl From for ord_subset::OrdVar { #[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")) } } @@ -501,7 +501,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(()) } diff --git a/src/dec64.rs b/src/dec64.rs index 5df2852b..2e62bc30 100644 --- a/src/dec64.rs +++ b/src/dec64.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; @@ -26,7 +26,7 @@ use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, R ShlAssign, Shr, ShrAssign, Deref}; use std::str::FromStr; use std::str::from_utf8_unchecked; -use d128; +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))); @@ -75,7 +75,7 @@ impl Into> for d64 { #[cfg(feature = "rustc-serialize")] impl Decodable for d64 { 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")) } } @@ -273,7 +273,7 @@ impl fmt::LowerExp for d64 { impl fmt::LowerHex for d64 { 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(()) } diff --git a/src/lib.rs b/src/lib.rs index 69b7d21f..7763a0cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,8 +29,8 @@ mod context; mod dec128; mod dec64; -pub use dec128::d128; -pub use dec64::d64; +pub use crate::dec128::d128; +pub use crate::dec64::d64; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/src/run_test.rs b/src/run_test.rs index 72e0bba0..f061880a 100644 --- a/src/run_test.rs +++ b/src/run_test.rs @@ -399,20 +399,20 @@ 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(()) From 743db5c451e7cdb608f1a0563f00c39ba2c0e30b Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 13 Dec 2018 16:24:12 -0500 Subject: [PATCH 61/75] upgrades run-test to test d128 and d64 - fixes one problem w/ d64 and d64 is GOOD TO GO --- Cargo.toml | 2 +- src/dec64.rs | 16 +++- src/run_test.rs | 215 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 182 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e452b0dc..55529fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.2.2" +version = "2.3.0" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" diff --git a/src/dec64.rs b/src/dec64.rs index 2e62bc30..e5152127 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -563,13 +563,14 @@ impl d64 { /// 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() != 32 { + if s.len() != 16 { Self::from_str("qNaN").unwrap() } else { unsafe { let mut res: d64 = uninitialized(); for (i, octet) in s.as_bytes().chunks(2).rev().enumerate() { - res.bytes[i] = match u8::from_str_radix(from_utf8_unchecked(octet), 8) { + //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(), }; @@ -1158,6 +1159,17 @@ mod tests { }} } + #[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)); diff --git a/src/run_test.rs b/src/run_test.rs index f061880a..96183d91 100644 --- a/src/run_test.rs +++ b/src/run_test.rs @@ -1,12 +1,16 @@ 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 +86,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 +141,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 +378,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); + } } } } @@ -419,16 +430,9 @@ impl<'a> fmt::Display for TestResult<'a> { } } -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 +442,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:ident, $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 +500,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 +567,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); From d8d693ce96ef550c48c54381e971a5a4a6dd32e8 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 16 Apr 2019 10:03:55 -0400 Subject: [PATCH 62/75] edit clap app settings to allow negative numbers --- src/bytes.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bytes.rs b/src/bytes.rs index 752290ce..eedd153e 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -4,7 +4,8 @@ use std::str::FromStr; fn main() { let args: clap::ArgMatches = clap::App::new("bytes") - .version("0.1") + .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)) From 79970e79ea2139fc060d3ee65e9f5f31024aaab5 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 16 Apr 2019 10:04:16 -0400 Subject: [PATCH 63/75] make infinity and neg_infinity const fn --- src/dec128.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 0408932a..36266c5b 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -765,6 +765,7 @@ impl d128 { } /// Creates a d128 from raw bytes. Endianess is host dependent. + #[cfg(target_endian = "little")] pub const fn from_raw_bytes(bytes: [u8; 16]) -> d128 { d128 { bytes } } @@ -853,15 +854,15 @@ impl d128 { } /// Returns the d128 representing +Infinity. - #[deprecated(since="2.1.6")] - pub fn infinity() -> d128 { - d128::from_str("Infinity").unwrap() + #[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. - #[deprecated(since="2.1.6")] - pub fn neg_infinity() -> d128 { - d128::from_str("-Infinity").unwrap() + #[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. From c8b1b169f07155138d664d02202b5031e2e79c28 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 16 Apr 2019 10:04:28 -0400 Subject: [PATCH 64/75] disable warnings --- src/run_test.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/run_test.rs b/src/run_test.rs index 96183d91..1f1de340 100644 --- a/src/run_test.rs +++ b/src/run_test.rs @@ -1,3 +1,6 @@ +#![allow(unused)] +#![allow(deprecated)] + extern crate decimal; use decimal::*; From 9006f892e3e424c1bef6be6a4d59884351b72b8f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 16 Apr 2019 10:04:58 -0400 Subject: [PATCH 65/75] bump ber --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 55529fe1..e70f3d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.0" +version = "2.3.1" authors = ["Alkis Evlogimenos "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From cc21d858a8cf7db303732f41ff88bb1336348c41 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 10 Jun 2019 08:43:32 -0400 Subject: [PATCH 66/75] fix test version of d64! macro to remove underscores before parsing lit --- src/dec64.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dec64.rs b/src/dec64.rs index e5152127..c88c95e4 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -1155,7 +1155,9 @@ mod tests { macro_rules! d64 { ($lit:expr) => {{ use std::str::FromStr; - $crate::d64::from_str(stringify!($lit)).expect("Invalid decimal float literal") + let lit = stringify!($lit); + let clean: String = lit.replace("_", ""); + $crate::d64::from_str(&clean).expect("Invalid decimal float literal") }} } From 73992ecb08b36640a29fc8aca5426911db2da26d Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 10 Jun 2019 08:44:08 -0400 Subject: [PATCH 67/75] add dyn for dynamic dispatch types --- src/dec128.rs | 4 ++-- src/dec64.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dec128.rs b/src/dec128.rs index 36266c5b..16667f2e 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -275,7 +275,7 @@ impl From for u128 { 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 => { + x @ 0 ..= 33 => { n = x as usize; r = val; } @@ -1260,7 +1260,7 @@ impl slog::Value for d128 { &self, _record: &slog::Record, key: slog::Key, - serializer: &mut slog::Serializer, + serializer: &mut dyn slog::Serializer, ) -> Result<(), slog::Error> { serializer.emit_arguments(key, &format_args!("{}", self)) } diff --git a/src/dec64.rs b/src/dec64.rs index c88c95e4..81e3cb31 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -973,7 +973,7 @@ impl slog::Value for d64 { &self, _record: &slog::Record, key: slog::Key, - serializer: &mut slog::Serializer, + serializer: &mut dyn slog::Serializer, ) -> Result<(), slog::Error> { serializer.emit_arguments(key, &format_args!("{}", self)) } From 7589bea9c6bd2c3a150c48cd889b992abe7e42ed Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 10 Jun 2019 08:44:44 -0400 Subject: [PATCH 68/75] replace transmute calls with as_bytes --- decimal-macros/src/plugin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decimal-macros/src/plugin.rs b/decimal-macros/src/plugin.rs index 94d2e381..22d6c093 100644 --- a/decimal-macros/src/plugin.rs +++ b/decimal-macros/src/plugin.rs @@ -125,7 +125,6 @@ mod d128_lit { } }; - //let num = unsafe { ::std::mem::transmute::(num) }; let num = num.as_bytes(); // Create array literal @@ -207,7 +206,8 @@ mod d64_lit { return DummyResult::any(sp); } }; - let num = unsafe { ::std::mem::transmute::(num) }; + + let num = num.as_bytes(); // Create array literal let mut vec = Vec::new(); From 061bea1780b29faa0a75fea94d2875e5571831fd Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 10 Jun 2019 08:45:00 -0400 Subject: [PATCH 69/75] misc cleanup --- Cargo.toml | 6 +----- decimal-macros/Cargo.toml | 2 -- decimal-macros/tests/test.rs | 8 +------- src/lib.rs | 1 + 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e70f3d8a..cda435fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,9 @@ [package] name = "decimal" version = "2.3.1" -authors = ["Alkis Evlogimenos "] +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" diff --git a/decimal-macros/Cargo.toml b/decimal-macros/Cargo.toml index c3c185e8..792d7fc9 100644 --- a/decimal-macros/Cargo.toml +++ b/decimal-macros/Cargo.toml @@ -15,5 +15,3 @@ proc-macro2 = "0.4" [build-dependencies] gcc = "0.3" - -[workspace] diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs index 3519dd46..dbb06f5c 100644 --- a/decimal-macros/tests/test.rs +++ b/decimal-macros/tests/test.rs @@ -1,6 +1,6 @@ #![feature(proc_macro_hygiene)] #![feature(const_fn)] -#![feature(const_let)] +#![allow(unused)] extern crate decimal_macros; extern crate decimal; @@ -8,12 +8,6 @@ extern crate decimal; use decimal::d128; use decimal_macros::*; -fn f(x: i32) -> i32 { x * 2 } - -macro_rules! f { - ($x:expr) => { x * 2 } -} - #[test] fn basic_plugin_sanity_checks() { let a = d128!(0.1); diff --git a/src/lib.rs b/src/lib.rs index 7763a0cb..2b092511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(const_fn)] #![cfg_attr(test, feature(test))] +#![allow(deprecated)] #[macro_use] extern crate bitflags; From 0ccf9f25dac72a4d0090cf39380356f6b48b06ae Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 10 Jun 2019 08:45:39 -0400 Subject: [PATCH 70/75] bump ver to v2.3.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cda435fe..5ea5ec4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.1" +version = "2.3.2" authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" From 88e2fc3db7968ee27454751f3840a85d0142bc60 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Mon, 19 Aug 2019 23:02:47 -0400 Subject: [PATCH 71/75] remove stuff that requires nightly --- Cargo.toml | 5 +- src/dec128.rs | 125 ++++++++++++++++++-------------------------------- src/dec64.rs | 55 ++-------------------- src/lib.rs | 24 ++-------- 4 files changed, 55 insertions(+), 154 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ea5ec4d..91dfaef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.2" +version = "2.3.3" authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" @@ -36,6 +36,7 @@ approx = "0.1" [features] default = ["ord_subset", "serde", "slog"] +nightly = [] [build-dependencies] cc = "1" @@ -43,7 +44,7 @@ cc = "1" [dev-dependencies] serde_json = "1" rand = "0.4" -decimal-macros = { version = "0.2", path = "decimal-macros" } +#decimal-macros = { version = "0.2", path = "decimal-macros" } [profile.test] opt-level = 2 diff --git a/src/dec128.rs b/src/dec128.rs index 16667f2e..852bc778 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -197,13 +197,12 @@ impl From for f32 { /// /// # Examples /// ``` -/// #![feature(proc_macro_hygiene)] /// extern crate decimal; -/// extern crate decimal_macros; -/// use decimal_macros::*; +/// use decimal::d128; +/// use std::str::FromStr; /// /// fn main() { -/// let x = d128!(12345); +/// let x = d128::from_str("12345").unwrap(); /// assert_eq!(u64::from(x), 12345u64); /// } /// ``` @@ -256,13 +255,12 @@ impl From for u64 { /// /// # Examples /// ``` -/// #![feature(proc_macro_hygiene)] /// extern crate decimal; -/// extern crate decimal_macros; -/// use decimal_macros::*; +/// use decimal::d128; +/// use std::str::FromStr; /// /// fn main() { -/// let x = d128!(12345); +/// let x = d128::from_str("12345").unwrap(); /// assert_eq!(u128::from(x), 12345u128); /// } /// ``` @@ -349,30 +347,28 @@ impl From for d128 { /// /// # Examples /// ``` -/// #![feature(proc_macro_hygiene)] /// extern crate decimal; -/// extern crate decimal_macros; -/// use decimal_macros::*; /// use decimal::d128; +/// use std::str::FromStr; /// /// fn main() { -/// let a: u128 = 12345; -/// assert_eq!(d128::from(a), d128!(12345)); +/// let a = d128::from_str("12345").unwrap(); +/// assert_eq!(d128::from(a), a); /// /// let b: u128 = 340282366920938463463374607431768211455; -/// assert_eq!(d128::from(b), d128!(340282366920938463463374607431768211455)); +/// assert_eq!(d128::from(b), d128::from_str("340282366920938463463374607431768211455").unwrap()); /// /// let c: u128 = 999_999_999_999_999_999_999_999_999_999_999; -/// assert_eq!(d128::from(c), d128!(999999999999999999999999999999999)); +/// assert_eq!(d128::from(c), d128::from_str("999999999999999999999999999999999").unwrap()); /// /// let d: u128 = 9_999_999_999_999_999_999_999_999_999_999_999; -/// assert_eq!(d128::from(d), d128!(9999999999999999999999999999999999)); +/// assert_eq!(d128::from(d), d128::from_str("9999999999999999999999999999999999").unwrap()); /// /// let e: u128 = 98_999_999_999_999_999_999_999_999_999_999_999; -/// assert_eq!(d128::from(e), d128!(98999999999999999999999999999999999)); +/// assert_eq!(d128::from(e), d128::from_str("98999999999999999999999999999999999").unwrap()); /// /// let f: u128 = 10_000_000_000_000_000_000_000_000_000_000_000; -/// assert_eq!(d128::from(f), d128!(10000000000000000000000000000000000)); +/// assert_eq!(d128::from(f), d128::from_str("10000000000000000000000000000000000").unwrap()); /// } /// ``` impl From for d128 { @@ -780,6 +776,7 @@ impl d128 { /// For benchmark comparisons. /// + #[allow(dead_code)] #[cfg(test)] #[inline] fn baseline_from_f64_lossy(x: f64) -> d128 { @@ -1001,65 +998,16 @@ impl d128 { /// 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) }) } @@ -1079,20 +1027,6 @@ 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) }) } @@ -1418,6 +1352,7 @@ mod tests { use rand::{self, Rng}; use rand::distributions::{IndependentSample, Range}; + #[cfg(feature = "nightly")] #[allow(unused_imports)] use test::{black_box, Bencher}; @@ -1455,30 +1390,35 @@ mod tests { assert_relative_eq!(a, f32::from(d128!(4000.2340293842)), epsilon = 1e-6f32); } + #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f64_small(b: &mut Bencher) { let x = d128!(1.23456789); b.iter(|| f64::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f64_larger(b: &mut Bencher) { let x = d128!(4000.2340293842); b.iter(|| f64::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f32_small(b: &mut Bencher) { let x = d128!(1.23456789); b.iter(|| f32::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f32_larger(b: &mut Bencher) { let x = d128!(4000.2340293842); b.iter(|| f32::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn truncates_long_dec_to_int(b: &mut Bencher) { let d = d128!(51.55933794806056096535214001488143); @@ -1597,6 +1537,7 @@ mod tests { check!(18446744073709551615); } + #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_u64_1e8_of_float(b: &mut Bencher) { let x = 0.00012345f64; @@ -1621,6 +1562,7 @@ mod tests { } + #[cfg(feature = "nightly")] #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_u64_1e8_of_float_simd(b: &mut Bencher) { @@ -1650,6 +1592,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_f32_simd(b: &mut Bencher) { @@ -1667,6 +1610,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_f32(b: &mut Bencher) { let x = 0.00012345f32; @@ -1680,6 +1624,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_f64(b: &mut Bencher) { let x = 0.00012345f64; @@ -1693,6 +1638,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_d128(b: &mut Bencher) { let x = d128!(0.00012345); @@ -1706,6 +1652,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn d128_mult_1e8_convert_u64_as_i64_mult_neg_one(b: &mut Bencher) { let price = d128!(6128.1234567); @@ -1717,24 +1664,28 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn d128_to_u64(b: &mut Bencher) { let x = d128!(12345); b.iter(|| u64::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn d128_to_u128(b: &mut Bencher) { let x = d128!(12345); b.iter(|| u128::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn d128_to_u128_max_digits(b: &mut Bencher) { let x = d128!(51.55933794806056096535214001488143); b.iter(|| u128::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn d128_to_u128_max_digits_preemptive_truncate(b: &mut Bencher) { let x = d128!(51.55933794806056096535214001488143); @@ -1742,12 +1693,14 @@ mod tests { b.iter(|| u128::from(x.truncate(ONE))); } + #[cfg(feature = "nightly")] #[bench] fn u64_to_d128(b: &mut Bencher) { let x = 12345u64; b.iter(|| d128::from(x)); } + #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_u64_1e8(b: &mut Bencher) { let x = 1.2345f32; @@ -1755,6 +1708,7 @@ mod tests { d128::from((x * 1e8) as i64) * d128!(1e-8) }); } + #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_u64_2_powi_27(b: &mut Bencher) { let x = 1.2345f32; @@ -1764,6 +1718,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_from_str(b: &mut Bencher) { let x = 1.2345f32; @@ -1879,6 +1834,7 @@ mod tests { assert_eq!(x.max(d128!(-100)), d128!(-100)); } + #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32_range(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1901,6 +1857,7 @@ mod tests { assert!(d <= d128!(1.2)); } + #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1910,6 +1867,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn random_number_via_u64(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1919,6 +1877,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_f64_baseline(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1928,6 +1887,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_f64(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1937,6 +1897,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_f32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1946,6 +1907,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1956,6 +1918,7 @@ mod tests { }); } + #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_i32(b: &mut Bencher) { let mut rng = rand::thread_rng(); diff --git a/src/dec64.rs b/src/dec64.rs index 81e3cb31..dd937918 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -735,66 +735,16 @@ impl d64 { /// 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) }) } @@ -1140,6 +1090,7 @@ mod tests { use rand::{self, Rng}; use rand::distributions::{IndependentSample, Range}; + #[cfg(feature = "nightly")] #[allow(unused_imports)] use test::{black_box, Bencher}; @@ -1178,6 +1129,7 @@ mod tests { assert_eq!(d64::from(d128!(1.23456)), d64!(1.23456)); } + #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000(b: &mut Bencher) { let x = d64!(0.00012345); @@ -1238,6 +1190,7 @@ mod tests { assert_eq!(x.max(d64!(-100)), d64!(-100)); } + #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32_range(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1260,6 +1213,7 @@ mod tests { assert!(d <= d64!(1.2)); } + #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1269,6 +1223,7 @@ mod tests { }); } + // #[cfg(feature = "nightly")] // #[bench] // fn random_number_via_u32(b: &mut Bencher) { // let mut rng = rand::thread_rng(); diff --git a/src/lib.rs b/src/lib.rs index 2b092511..21e415f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -#![feature(const_fn)] -#![cfg_attr(test, feature(test))] +#![cfg_attr(feature = "nightly", feature(const_fn))] +#![cfg_attr(all(feature = "nightly", test), feature(test))] #![allow(deprecated)] #[macro_use] @@ -16,7 +16,7 @@ extern crate serde; extern crate serde_json; #[cfg(test)] extern crate rand; -#[cfg(test)] +#[cfg(all(test, feature = "nightly"))] extern crate test; #[cfg(feature = "faster")] extern crate faster; @@ -70,24 +70,6 @@ bitflags! { /// specific points to check the validity of the computation. Each thread gets its own status. /// The status is initially empty. /// - /// # Examples - /// - /// ``` - /// # #![feature(proc_macro_hygiene)] - /// # extern crate decimal_macros; - /// # extern crate decimal; - /// # use decimal_macros::*; - /// # use decimal::d128; - /// # use decimal::Status; - /// # fn main() { - /// assert_eq!(d128::get_status(), Status::empty()); - /// let _ = d128!(1) / &d128!(0); - /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); - /// let _ = d128!(0) / &d128!(0); - /// assert!(d128::get_status().contains(decimal::Status::DIVISION_UNDEFINED)); - /// // The previous status flag was not cleared! - /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); - /// # } pub struct Status: ::libc::uint32_t { /// Conversion syntax error. const CONVERSION_SYNTAX = 0x00000001; From be45f5345f323e08029756cfe0d839422b2e3285 Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Sat, 25 Apr 2020 03:15:31 -0400 Subject: [PATCH 72/75] adds correct From and From for d64, though less than ideal --- Cargo.toml | 2 +- src/dec128.rs | 11 ++++- src/dec64.rs | 110 +++++++++++++++++++++++++++++------------------- src/run_test.rs | 2 +- 4 files changed, 78 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ea5ec4d..15cab3ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.2" +version = "2.3.3" authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" diff --git a/src/dec128.rs b/src/dec128.rs index 16667f2e..181c24b6 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -1911,7 +1911,7 @@ mod tests { } #[bench] - fn random_number_via_u64(b: &mut Bencher) { + fn d128_via_random_u64(b: &mut Bencher) { let mut rng = rand::thread_rng(); b.iter(|| { let d: d128 = rng.gen::().into(); @@ -1919,6 +1919,15 @@ mod tests { }); } + #[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(); diff --git a/src/dec64.rs b/src/dec64.rs index 81e3cb31..c0a26759 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -151,33 +151,37 @@ impl From for d64 { } } -// /// Converts an u64 to d64. The result is exact and no error is possible. -// impl From for d64 { -// fn from(mut val: u64) -> d64 { -// let mut bcd = [0; 34]; -// let mut i = 33; -// while val > 0 { -// bcd[i] = (val % 10) as u8; -// val /= 10; -// i -= 1; -// } -// unsafe { -// let mut res: d64 = uninitialized(); -// *decDoubleFromBCD(&mut res, 0, bcd.as_ptr(), 0) -// } -// } -// } - -// /// 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) -// } -// } -// } +/// Converts an u64 to d64. The result is exact and no error is possible. +impl From for d64 { + fn from(mut 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: d64 = uninitialized(); + *decDoubleFromBCD(&mut res, 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 { @@ -993,7 +997,7 @@ extern "C" { 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 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; @@ -1405,23 +1409,23 @@ mod tests { 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_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 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() { @@ -1431,4 +1435,22 @@ mod tests { 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/run_test.rs b/src/run_test.rs index 1f1de340..ebb9c049 100644 --- a/src/run_test.rs +++ b/src/run_test.rs @@ -470,7 +470,7 @@ fn parse_operand(s: &str) -> T } macro_rules! simple_op { - ($d:ident, $test:ident, $res:ident = $func:ident($($arg:ident),+)) => { + ($d:ty, $test:ident, $res:ident = $func:ident($($arg:ident),+)) => { { type D = $d; From 9dace11eb6740651ad8c971640174d2d3f3daf9c Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Jun 2020 00:59:00 -0400 Subject: [PATCH 73/75] add a somewhat lame From for d128 impl --- Cargo.toml | 2 +- src/dec128.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 15cab3ee..e38d752f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.3" +version = "2.3.4" authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" diff --git a/src/dec128.rs b/src/dec128.rs index 181c24b6..20f1d4af 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -151,6 +151,12 @@ impl From for d128 { } } +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 { @@ -1455,6 +1461,38 @@ mod tests { 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); From 643e5ea4901981747bfd5aab2064c3f4991a5e7a Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Thu, 4 Jun 2020 01:04:20 -0400 Subject: [PATCH 74/75] Revert "Merge branch 'v2.3.x' of github.com:jonathanstrong/decimal into v2.3.x" This reverts commit 9e7b016d1b49630030877da0a6ca8cd121833e11, reversing changes made to 9dace11eb6740651ad8c971640174d2d3f3daf9c. --- Cargo.toml | 3 +- src/dec128.rs | 128 ++++++++++++++++++++++++++++++++------------------ src/dec64.rs | 55 ++++++++++++++++++++-- src/lib.rs | 24 ++++++++-- 4 files changed, 153 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6550d9c6..e38d752f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ approx = "0.1" [features] default = ["ord_subset", "serde", "slog"] -nightly = [] [build-dependencies] cc = "1" @@ -44,7 +43,7 @@ cc = "1" [dev-dependencies] serde_json = "1" rand = "0.4" -#decimal-macros = { version = "0.2", path = "decimal-macros" } +decimal-macros = { version = "0.2", path = "decimal-macros" } [profile.test] opt-level = 2 diff --git a/src/dec128.rs b/src/dec128.rs index 950048dd..20f1d4af 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -203,12 +203,13 @@ impl From for f32 { /// /// # Examples /// ``` +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; -/// use decimal::d128; -/// use std::str::FromStr; +/// extern crate decimal_macros; +/// use decimal_macros::*; /// /// fn main() { -/// let x = d128::from_str("12345").unwrap(); +/// let x = d128!(12345); /// assert_eq!(u64::from(x), 12345u64); /// } /// ``` @@ -261,12 +262,13 @@ impl From for u64 { /// /// # Examples /// ``` +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; -/// use decimal::d128; -/// use std::str::FromStr; +/// extern crate decimal_macros; +/// use decimal_macros::*; /// /// fn main() { -/// let x = d128::from_str("12345").unwrap(); +/// let x = d128!(12345); /// assert_eq!(u128::from(x), 12345u128); /// } /// ``` @@ -353,28 +355,30 @@ impl From for d128 { /// /// # Examples /// ``` +/// #![feature(proc_macro_hygiene)] /// extern crate decimal; +/// extern crate decimal_macros; +/// use decimal_macros::*; /// use decimal::d128; -/// use std::str::FromStr; /// /// fn main() { -/// let a = d128::from_str("12345").unwrap(); -/// assert_eq!(d128::from(a), a); +/// let a: u128 = 12345; +/// assert_eq!(d128::from(a), d128!(12345)); /// /// let b: u128 = 340282366920938463463374607431768211455; -/// assert_eq!(d128::from(b), d128::from_str("340282366920938463463374607431768211455").unwrap()); +/// 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::from_str("999999999999999999999999999999999").unwrap()); +/// 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::from_str("9999999999999999999999999999999999").unwrap()); +/// 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::from_str("98999999999999999999999999999999999").unwrap()); +/// 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::from_str("10000000000000000000000000000000000").unwrap()); +/// assert_eq!(d128::from(f), d128!(10000000000000000000000000000000000)); /// } /// ``` impl From for d128 { @@ -782,7 +786,6 @@ impl d128 { /// For benchmark comparisons. /// - #[allow(dead_code)] #[cfg(test)] #[inline] fn baseline_from_f64_lossy(x: f64) -> d128 { @@ -1004,16 +1007,65 @@ impl d128 { /// 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) }) } @@ -1033,6 +1085,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) }) } @@ -1358,7 +1424,6 @@ mod tests { use rand::{self, Rng}; use rand::distributions::{IndependentSample, Range}; - #[cfg(feature = "nightly")] #[allow(unused_imports)] use test::{black_box, Bencher}; @@ -1396,7 +1461,6 @@ mod tests { assert_relative_eq!(a, f32::from(d128!(4000.2340293842)), epsilon = 1e-6f32); } -<<<<<<< HEAD #[test] fn verify_from_f64_for_d128_behaves_well() { let test = |x: &str| { @@ -1429,37 +1493,30 @@ mod tests { b.iter(|| d128::from(x)); } -======= - #[cfg(feature = "nightly")] ->>>>>>> 88e2fc3db7968ee27454751f3840a85d0142bc60 #[bench] fn bench_d128_to_f64_small(b: &mut Bencher) { let x = d128!(1.23456789); b.iter(|| f64::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f64_larger(b: &mut Bencher) { let x = d128!(4000.2340293842); b.iter(|| f64::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f32_small(b: &mut Bencher) { let x = d128!(1.23456789); b.iter(|| f32::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn bench_d128_to_f32_larger(b: &mut Bencher) { let x = d128!(4000.2340293842); b.iter(|| f32::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn truncates_long_dec_to_int(b: &mut Bencher) { let d = d128!(51.55933794806056096535214001488143); @@ -1578,7 +1635,6 @@ mod tests { check!(18446744073709551615); } - #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_u64_1e8_of_float(b: &mut Bencher) { let x = 0.00012345f64; @@ -1603,7 +1659,6 @@ mod tests { } - #[cfg(feature = "nightly")] #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_u64_1e8_of_float_simd(b: &mut Bencher) { @@ -1633,7 +1688,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[cfg(feature = "faster")] #[bench] fn sums_vec_of_100_000_f32_simd(b: &mut Bencher) { @@ -1651,7 +1705,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_f32(b: &mut Bencher) { let x = 0.00012345f32; @@ -1665,7 +1718,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_f64(b: &mut Bencher) { let x = 0.00012345f64; @@ -1679,7 +1731,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000_d128(b: &mut Bencher) { let x = d128!(0.00012345); @@ -1693,7 +1744,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn d128_mult_1e8_convert_u64_as_i64_mult_neg_one(b: &mut Bencher) { let price = d128!(6128.1234567); @@ -1705,28 +1755,24 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn d128_to_u64(b: &mut Bencher) { let x = d128!(12345); b.iter(|| u64::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn d128_to_u128(b: &mut Bencher) { let x = d128!(12345); b.iter(|| u128::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn d128_to_u128_max_digits(b: &mut Bencher) { let x = d128!(51.55933794806056096535214001488143); b.iter(|| u128::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn d128_to_u128_max_digits_preemptive_truncate(b: &mut Bencher) { let x = d128!(51.55933794806056096535214001488143); @@ -1734,14 +1780,12 @@ mod tests { b.iter(|| u128::from(x.truncate(ONE))); } - #[cfg(feature = "nightly")] #[bench] fn u64_to_d128(b: &mut Bencher) { let x = 12345u64; b.iter(|| d128::from(x)); } - #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_u64_1e8(b: &mut Bencher) { let x = 1.2345f32; @@ -1749,7 +1793,6 @@ mod tests { d128::from((x * 1e8) as i64) * d128!(1e-8) }); } - #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_u64_2_powi_27(b: &mut Bencher) { let x = 1.2345f32; @@ -1759,7 +1802,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn d128_from_f32_via_from_str(b: &mut Bencher) { let x = 1.2345f32; @@ -1875,7 +1917,6 @@ mod tests { assert_eq!(x.max(d128!(-100)), d128!(-100)); } - #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32_range(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1898,7 +1939,6 @@ mod tests { assert!(d <= d128!(1.2)); } - #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1908,7 +1948,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn d128_via_random_u64(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1918,7 +1957,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn d128_via_random_i64(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1937,7 +1975,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_f64(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1947,7 +1984,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_f32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1957,7 +1993,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1968,7 +2003,6 @@ mod tests { }); } - #[cfg(feature = "nightly")] #[bench] fn rand_0_1_via_i32(b: &mut Bencher) { let mut rng = rand::thread_rng(); diff --git a/src/dec64.rs b/src/dec64.rs index 719cdfd0..c0a26759 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -739,16 +739,66 @@ impl d64 { /// 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) }) } @@ -1094,7 +1144,6 @@ mod tests { use rand::{self, Rng}; use rand::distributions::{IndependentSample, Range}; - #[cfg(feature = "nightly")] #[allow(unused_imports)] use test::{black_box, Bencher}; @@ -1133,7 +1182,6 @@ mod tests { assert_eq!(d64::from(d128!(1.23456)), d64!(1.23456)); } - #[cfg(feature = "nightly")] #[bench] fn sums_vec_of_100_000(b: &mut Bencher) { let x = d64!(0.00012345); @@ -1194,7 +1242,6 @@ mod tests { assert_eq!(x.max(d64!(-100)), d64!(-100)); } - #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32_range(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1217,7 +1264,6 @@ mod tests { assert!(d <= d64!(1.2)); } - #[cfg(feature = "nightly")] #[bench] fn random_number_via_u32(b: &mut Bencher) { let mut rng = rand::thread_rng(); @@ -1227,7 +1273,6 @@ mod tests { }); } - // #[cfg(feature = "nightly")] // #[bench] // fn random_number_via_u32(b: &mut Bencher) { // let mut rng = rand::thread_rng(); diff --git a/src/lib.rs b/src/lib.rs index 21e415f2..2b092511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(feature = "nightly", feature(const_fn))] -#![cfg_attr(all(feature = "nightly", test), feature(test))] +#![feature(const_fn)] +#![cfg_attr(test, feature(test))] #![allow(deprecated)] #[macro_use] @@ -16,7 +16,7 @@ extern crate serde; extern crate serde_json; #[cfg(test)] extern crate rand; -#[cfg(all(test, feature = "nightly"))] +#[cfg(test)] extern crate test; #[cfg(feature = "faster")] extern crate faster; @@ -70,6 +70,24 @@ bitflags! { /// specific points to check the validity of the computation. Each thread gets its own status. /// The status is initially empty. /// + /// # Examples + /// + /// ``` + /// # #![feature(proc_macro_hygiene)] + /// # extern crate decimal_macros; + /// # extern crate decimal; + /// # use decimal_macros::*; + /// # use decimal::d128; + /// # use decimal::Status; + /// # fn main() { + /// assert_eq!(d128::get_status(), Status::empty()); + /// let _ = d128!(1) / &d128!(0); + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); + /// let _ = d128!(0) / &d128!(0); + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_UNDEFINED)); + /// // The previous status flag was not cleared! + /// assert!(d128::get_status().contains(decimal::Status::DIVISION_BY_ZERO)); + /// # } pub struct Status: ::libc::uint32_t { /// Conversion syntax error. const CONVERSION_SYNTAX = 0x00000001; From 5c816c7ac61b0447b4ed05612a077e3a07ee3f6f Mon Sep 17 00:00:00 2001 From: Jonathan Strong Date: Tue, 29 Sep 2020 22:34:28 -0400 Subject: [PATCH 75/75] convert mem::uninitialized to mem::MaybeUninit --- Cargo.toml | 2 +- src/dec128.rs | 111 ++++++++++++++++++++++++++++---------------------- src/dec64.rs | 81 +++++++++++++++++++----------------- 3 files changed, 106 insertions(+), 88 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e38d752f..56a3a929 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "decimal" -version = "2.3.4" +version = "2.4.0" authors = ["Alkis Evlogimenos ", "Jonathan Strong "] build = "build.rs" description = "Decimal floating point arithmetic for Rust" diff --git a/src/dec128.rs b/src/dec128.rs index 20f1d4af..f9965fec 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -12,6 +12,7 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use serde; #[cfg(feature = "slog")] use slog; +use std::mem::MaybeUninit; use std::borrow::Borrow; use std::cell::RefCell; use std::default::Default; @@ -19,7 +20,6 @@ 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, @@ -135,8 +135,8 @@ 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) } } } @@ -145,8 +145,8 @@ 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) } } } @@ -218,8 +218,8 @@ impl From for u64 { fn from(val: d128) -> u64 { debug_assert!(val >= d128::zero()); //let mut bcd = [0; 34]; - let mut bcd: [u8; 34] = unsafe { uninitialized() }; - let mut exp: i32 = unsafe { uninitialized() }; + 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); @@ -294,8 +294,8 @@ impl From for u128 { debug_assert!(n < 34); //let mut bcd = [0; 34]; - let mut bcd: [u8; 34] = unsafe { uninitialized() }; - let mut exp: i32 = unsafe { uninitialized() }; + 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); @@ -337,7 +337,7 @@ impl From for u128 { /// 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; @@ -345,8 +345,8 @@ 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) } } } @@ -396,8 +396,8 @@ impl From for d128 { } 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) } } } @@ -442,12 +442,12 @@ 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) }) } } @@ -550,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) + } }) } } @@ -595,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) + } }) } } @@ -669,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) } }) } @@ -708,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) } }) } @@ -737,16 +743,17 @@ 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: Context = uninitialized(); - let mut ctx = *decContextDefault(&mut res, 128); + let mut res: MaybeUninit = MaybeUninit::uninit(); + let mut ctx = *decContextDefault(res.as_mut_ptr(), 128); decContextSetRounding(&mut ctx, rounding as u32); ctx } @@ -837,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, @@ -854,8 +861,8 @@ 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()) } } @@ -935,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) }) @@ -951,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) }) @@ -968,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) }) @@ -983,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) }) @@ -1118,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) }) } @@ -1128,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) }) } diff --git a/src/dec64.rs b/src/dec64.rs index c0a26759..e538691f 100644 --- a/src/dec64.rs +++ b/src/dec64.rs @@ -19,7 +19,7 @@ use std::ffi::{CStr, CString}; use std::fmt; use std::hash::{Hash, Hasher}; use std::iter::Sum; -use std::mem::uninitialized; +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, @@ -135,8 +135,8 @@ impl<'de> serde::de::Visitor<'de> for d64Visitor { impl From for d64 { fn from(val: i32) -> d64 { unsafe { - let mut res: d64 = uninitialized(); - *decDoubleFromInt32(&mut res, val) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromInt32(res.as_mut_ptr(), val) } } } @@ -145,15 +145,15 @@ impl From for d64 { impl From for d64 { fn from(val: u32) -> d64 { unsafe { - let mut res: d64 = uninitialized(); - *decDoubleFromUInt32(&mut res, val) + 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(mut val: u64) -> d64 { + fn from(val: u64) -> d64 { /* let mut bcd = [0u8; 16]; let mut i = 15; @@ -163,8 +163,8 @@ impl From for d64 { i -= 1; } unsafe { - let mut res: d64 = uninitialized(); - *decDoubleFromBCD(&mut res, 0, bcd.as_ptr(), 0) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromBCD(res.as_mut_ptr(), 0, bcd.as_ptr(), 0) } */ let wider = d128::from(val); @@ -212,11 +212,11 @@ impl FromStr for d64 { Ok(cstr) => cstr, }; d64::with_context(|ctx| { - let mut res: d64; - unsafe { - res = uninitialized(); - decDoubleFromString(&mut res, cstr.as_ptr(), 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) }) } @@ -320,7 +320,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) + } }) } } @@ -365,7 +368,9 @@ 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) } }) } } @@ -439,8 +444,8 @@ impl<'a> Shl for &'a d64 { let shift = d64::from(amount as u32); d64::with_context(|ctx| { unsafe { - let mut res: d64 = uninitialized(); - *decDoubleShift(&mut res, self, &shift, ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleShift(res.as_mut_ptr(), self, &shift, ctx) } }) } @@ -478,8 +483,8 @@ impl<'a> Shr for &'a d64 { let shift = -d64::from(amount as u32); d64::with_context(|ctx| { unsafe { - let mut res: d64 = uninitialized(); - *decDoubleShift(&mut res, self, &shift, ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleShift(res.as_mut_ptr(), self, &shift, ctx) } }) } @@ -507,16 +512,16 @@ impl Sum for d64 where T: Borrow { impl d64 { fn default_context() -> Context { unsafe { - let mut res: Context = uninitialized(); - *decContextDefault(&mut res, 64) + 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: Context = uninitialized(); - let mut ctx = *decContextDefault(&mut res, 64); + let mut res: MaybeUninit = MaybeUninit::uninit(); + let mut ctx = *decContextDefault(res.as_mut_ptr(), 64); decContextSetRounding(&mut ctx, rounding as u32); ctx } @@ -571,7 +576,7 @@ impl d64 { Self::from_str("qNaN").unwrap() } else { unsafe { - let mut res: d64 = uninitialized(); + 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) { @@ -589,8 +594,8 @@ impl d64 { /// Returns the d64 representing +0. pub fn zero() -> d64 { unsafe { - let mut res = uninitialized(); - *decDoubleZero(&mut res) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleZero(res.as_mut_ptr()) } } @@ -668,8 +673,8 @@ impl d64 { /// 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 = uninitialized(); - let mut num_exp: decNumber = uninitialized(); + 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); @@ -684,8 +689,8 @@ impl d64 { /// 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 = uninitialized(); - let mut num_exp: decNumber = uninitialized(); + 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); @@ -701,7 +706,7 @@ impl d64 { /// apply as described above. pub fn ln(mut self) -> d64 { d64::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); + 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) @@ -716,7 +721,7 @@ impl d64 { /// precision and range apply as described above. pub fn log10(mut self) -> d64 { d64::with_context(|ctx| unsafe { - let mut num_self: decNumber = uninitialized(); + 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) @@ -837,8 +842,8 @@ impl d64 { /// only if `self` or `other` is a NaN. pub fn compare>(&self, other: O) -> d64 { d64::with_context(|ctx| unsafe { - let mut res: d64 = uninitialized(); - *decDoubleCompare(&mut res, self, other.as_ref(), ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleCompare(res.as_mut_ptr(), self, other.as_ref(), ctx) }) } @@ -847,8 +852,8 @@ impl d64 { /// 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: d64 = uninitialized(); - *decDoubleCompareTotal(&mut res, self, other.as_ref(), ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleCompareTotal(res.as_mut_ptr(), self, other.as_ref(), ctx) }) } @@ -986,8 +991,8 @@ impl slog::Value for d64 { impl From for d64 { fn from(val: d128) -> Self { d64::with_context(|ctx| unsafe { - let mut res: d64 = uninitialized(); - *decDoubleFromWider(&mut res, &val, ctx) + let mut res: MaybeUninit = MaybeUninit::uninit(); + *decDoubleFromWider(res.as_mut_ptr(), &val, ctx) }) } }