From 31f5c1147189622c8150a9ee95387597c64028e2 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 26 Aug 2021 17:45:39 -0400 Subject: [PATCH 001/100] Begin v0.4.0 development branch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b8fc329..e9db4f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bigdecimal" -version = "0.3.1" +version = "0.4.0+dev" authors = ["Andrew Kubera"] description = "Arbitrary precision decimal numbers" documentation = "https://docs.rs/bigdecimal" From ffff118c58ed06891924a58a51399b185c928d6e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 02:43:45 -0400 Subject: [PATCH 002/100] Change minimum Rust versions of CI tests --- .circleci/config.yml | 6 +----- .gitlab-ci.yml | 24 +++++------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5ca96ea..147cd27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,15 +83,11 @@ workflows: version: 2 build-and-test: jobs: - - build-and-test: - name: build-and-test-1.34.0 - rust-version: "1.34.0" - debian-version: "stretch" - build-and-test: matrix: parameters: rust-version: - - "1.40.0" + - "1.42.0" - "1.50.0" - "1.54.0" - build-and-test: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 57406fe..c1cac24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,30 +60,16 @@ cargo:test-nightly: <<: *cargo-test-script -cargo:build-1.34: +cargo:build-1.43: stage: build - image: "akubera/rust-kcov:1.34.2-stretch" + image: "akubera/rust-kcov:1.43.1-buster" <<: *cargo-build-script -cargo:test-1.34: +cargo:test·1.43: stage: test needs: - - cargo:build-1.34 - image: "akubera/rust-kcov:1.34.2-stretch" - allow_failure: true - <<: *cargo-test-script - - -cargo:build-1.42: - stage: build - image: "akubera/rust-kcov:1.42.0-buster" - <<: *cargo-build-script - -cargo:test·1.42: - stage: test - needs: - - "cargo:build-1.42" - image: "akubera/rust-kcov:1.42.0-buster" + - "cargo:build-1.43" + image: "akubera/rust-kcov:1.43.1-buster" <<: *cargo-test-script From 161a18fe0c977dfe4db6a7b88b445be50dd4a04f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 02:44:20 -0400 Subject: [PATCH 003/100] Update minimum supported version badge in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 830aa32..f60558a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![crate](https://img.shields.io/crates/v/bigdecimal.svg)](https://crates.io/crates/bigdecimal) [![Documentation](https://docs.rs/bigdecimal/badge.svg)](https://docs.rs/bigdecimal) -[![minimum rustc 1.34](https://img.shields.io/badge/rustc-1.34+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) +[![minimum rustc 1.43](https://img.shields.io/badge/rustc-1.43+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [![coverage](https://gitlab.com/akubera/bigdecimal-rs/badges/master/coverage.svg)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) [![build status - master](https://gitlab.com/akubera/bigdecimal-rs/badges/master/pipeline.svg?ignore_skipped=true)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) From f48606b703c9298595dcc3c3da3e37ba719379fb Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 20:14:13 -0400 Subject: [PATCH 004/100] Remove .cargo/bin from gitlab-ci cache --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1cac24..cd40ac9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,6 @@ cache: # should we only share cache between jobs on one tag? # key: ${CI_COMMIT_REF_SLUG} paths: - - .cargo/bin - .cargo/registry/index - .cargo/registry/cache - target/debug/deps From ef1036bc27130846ee06c76d51d40e3da88a992e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 20:15:22 -0400 Subject: [PATCH 005/100] Replace unicode cdot in gitlab job names with "-" --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cd40ac9..936224c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,7 +64,7 @@ cargo:build-1.43: image: "akubera/rust-kcov:1.43.1-buster" <<: *cargo-build-script -cargo:test·1.43: +cargo:test-1.43: stage: test needs: - "cargo:build-1.43" @@ -77,7 +77,7 @@ cargo:build-1.54: image: "akubera/rust-kcov:1.54.0-bullseye" <<: *cargo-build-script -cargo:test·1.54: +cargo:test-1.54: stage: test needs: - "cargo:build-1.54" From 95461da40f90f35e6ab13023cd1da7cf9aef3aa1 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 20:18:09 -0400 Subject: [PATCH 006/100] Update minimum version in .circleci and disable lint-check job --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 147cd27..40fd060 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -87,7 +87,7 @@ workflows: matrix: parameters: rust-version: - - "1.42.0" + - "1.43.0" - "1.50.0" - "1.54.0" - build-and-test: @@ -103,4 +103,4 @@ workflows: rust-version: "1.54.0" requires: - build-and-test-1.54.0 - - lint-check + # - lint-check From 7d928f5e2e501ec64066ebf1f7725912d5107cad Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 15:46:31 +0900 Subject: [PATCH 007/100] fix typos --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7c79e95..4ec8998 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1673,7 +1673,7 @@ impl<'a> Sum<&'a BigDecimal> for BigDecimal { impl fmt::Display for BigDecimal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Aquire the absolute integer as a decimal string + // Acquire the absolute integer as a decimal string let mut abs_int = self.int_val.abs().to_str_radix(10); // Split the representation at the decimal point From a125441941da8386773f203dc8441bf96bcd06a2 Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 15:59:19 +0900 Subject: [PATCH 008/100] write a test for to_i128 which will be failred --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4ec8998..c5be214 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2154,6 +2154,22 @@ mod bigdecimal_tests { } } + #[test] + fn test_to_i128() { + let vals = vec![ + ("12.34", 12), + ("3.14", 3), + ("50", 50), + ("170141183460469231731687303715884105727", 170141183460469231731687303715884105727), + ("0.001", 0), + ]; + for (s, ans) in vals { + let calculated = BigDecimal::from_str(s).unwrap().to_i128().unwrap(); + + assert_eq!(ans, calculated); + } + } + #[test] fn test_to_f64() { let vals = vec![ From c189292c99c325d53db7eb50afdfc4389c1a7a45 Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 15:59:54 +0900 Subject: [PATCH 009/100] override to_i128 for BigDecimal to pass the test --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c5be214..fc5f02d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1810,6 +1810,12 @@ impl ToPrimitive for BigDecimal { Sign::NoSign => Some(0), } } + fn to_i128(&self) -> Option { + match self.sign() { + Sign::Minus | Sign::Plus => self.with_scale(0).int_val.to_i128(), + Sign::NoSign => Some(0), + } + } fn to_u64(&self) -> Option { match self.sign() { Sign::Plus => self.with_scale(0).int_val.to_u64(), From 79cf57ece5e5a8307bb3a7cbf2d13dfacbec40bd Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 16:02:25 +0900 Subject: [PATCH 010/100] add negative max case --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fc5f02d..0db5198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2163,10 +2163,11 @@ mod bigdecimal_tests { #[test] fn test_to_i128() { let vals = vec![ + ("170141183460469231731687303715884105727", 170141183460469231731687303715884105727), + ("-170141183460469231731687303715884105728", -170141183460469231731687303715884105728), ("12.34", 12), ("3.14", 3), ("50", 50), - ("170141183460469231731687303715884105727", 170141183460469231731687303715884105727), ("0.001", 0), ]; for (s, ans) in vals { From 0596dbe0bfb47a62e5ba45102be85404befb1de3 Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 16:04:49 +0900 Subject: [PATCH 011/100] write a test case for to_u128 which will fail --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0db5198..3b1d625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2177,6 +2177,22 @@ mod bigdecimal_tests { } } + #[test] + fn test_to_u128() { + let vals = vec![ + ("340282366920938463463374607431768211455", 340282366920938463463374607431768211455), + ("12.34", 12), + ("3.14", 3), + ("50", 50), + ("0.001", 0), + ]; + for (s, ans) in vals { + let calculated = BigDecimal::from_str(s).unwrap().to_u128().unwrap(); + + assert_eq!(ans, calculated); + } + } + #[test] fn test_to_f64() { let vals = vec![ From cf4f850476ae3ab09d1b2c99abc8468fd3e4bacf Mon Sep 17 00:00:00 2001 From: Young <24crazyoung@gmail.com> Date: Sat, 16 Jul 2022 16:05:01 +0900 Subject: [PATCH 012/100] override to_u128 for BigDecimal --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3b1d625..037cd0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1823,6 +1823,13 @@ impl ToPrimitive for BigDecimal { Sign::Minus => None, } } + fn to_u128(&self) -> Option { + match self.sign() { + Sign::Plus => self.with_scale(0).int_val.to_u128(), + Sign::NoSign => Some(0), + Sign::Minus => None, + } + } fn to_f64(&self) -> Option { self.int_val.to_f64().map(|x| x * 10f64.powi(-self.scale as i32)) From 7cbee8e75016cb5100f458592237d251bb32e1e2 Mon Sep 17 00:00:00 2001 From: Daniel Bauman Date: Wed, 10 May 2023 12:21:01 -0700 Subject: [PATCH 013/100] Fix incorrect handling of _ in decimal BigDecimal supports '_' chars as visual seperators in number strings in the pre and post "." parts of the number. The counting of post "." digits however counts all characters but it should exclude '_' chars. The underscore support comes from BigDecimal's use of num-bigint https://github.com/rust-num/num-bigint/blob/6f2b8e0fc218dbd0f49bebb8db2d1a771fe6bafa/src/biguint/convert.rs#L246 https://github.com/akubera/bigdecimal-rs/issues/100 --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 037cd0c..fbbef79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1792,7 +1792,10 @@ impl Num for BigDecimal { // copy all trailing characters after '.' into the digits string digits.push_str(trail); - (digits, trail.len() as i64) + // count number of trailing digits + let trail_digits = trail.chars().filter(|c| *c != '_').count(); + + (digits, trail_digits as i64) } }; @@ -3035,6 +3038,7 @@ mod bigdecimal_tests { ("1.23E+3", 123, -1), ("1.23E-8", 123, 10), ("-1.23E-10", -123, 12), + ("-1_1.2_2", -1122, 2), ]; for &(source, val, scale) in vals.iter() { From d6579e57c998419f1b939ad5918109c6e4973545 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Thu, 11 May 2023 22:51:26 -0400 Subject: [PATCH 014/100] Add more test cases to test_from_str --- src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fbbef79..be41db7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3038,12 +3038,17 @@ mod bigdecimal_tests { ("1.23E+3", 123, -1), ("1.23E-8", 123, 10), ("-1.23E-10", -123, 12), + ("123_", 123, 0), + ("31_862_140.830_686_979", 31862140830686979, 9), ("-1_1.2_2", -1122, 2), + ("999.521_939", 999521939, 6), + ("679.35_84_03E-2", 679358403, 8), + ("271576662.__E4", 271576662, -4), ]; for &(source, val, scale) in vals.iter() { let x = BigDecimal::from_str(source).unwrap(); - assert_eq!(x.int_val.to_i32().unwrap(), val); + assert_eq!(x.int_val.to_i64().unwrap(), val); assert_eq!(x.scale, scale); } } From 98cae1d8bfa7e461fd5d27447492974781c7877c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 00:05:44 -0400 Subject: [PATCH 015/100] Change behavior of rust-features in build-and-test --- .circleci/config.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 40fd060..dd64f66 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: default: "buster" rust-features: type: string - default: "''" + default: "" docker: - image: rust:<< parameters.rust-version >>-<< parameters.debian-version >> environment: @@ -39,7 +39,7 @@ jobs: command: cargo build - run: name: Test - command: cargo test --features=<< parameters.rust-features >> + command: cargo test << parameters.rust-features >> upload-coverage: parameters: @@ -97,8 +97,12 @@ workflows: - "1.50.0" - "1.54.0" rust-features: - - "'serde'" - - "'serde,string-only'" + - "--features='serde'" + - "--features='serde,string-only'" + - build-and-test: + parameters: + rust-version: "1.69.0" + rust-features: "--no-default-features" - upload-coverage: rust-version: "1.54.0" requires: From bdba5d101b68fb717076096fdc206a9aeb81cf3f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 00:06:03 -0400 Subject: [PATCH 016/100] Update circleci/rust orb to 1.6.0 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dd64f66..7e4b4da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: # codecov: codecov/codecov@3.0.0 - rust: circleci/rust@1.5.0 + rust: circleci/rust@1.6.0 jobs: build-and-test: From 14955f06e6c1fac3a82efd12134c198744a3f529 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 00:06:41 -0400 Subject: [PATCH 017/100] Update default debian version bullseye and max rust versions to 1.69 --- .circleci/config.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e4b4da..00993db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,7 @@ jobs: type: string debian-version: type: string - default: "buster" + default: "bullseye" machine: true steps: - checkout @@ -68,7 +68,7 @@ jobs: lint-check: docker: - - image: cimg/rust:1.54.0 + - image: cimg/rust:1.69 steps: - checkout - rust/build @@ -90,6 +90,7 @@ workflows: - "1.43.0" - "1.50.0" - "1.54.0" + - "1.69.0" - build-and-test: matrix: parameters: @@ -104,7 +105,7 @@ workflows: rust-version: "1.69.0" rust-features: "--no-default-features" - upload-coverage: - rust-version: "1.54.0" + rust-version: "1.69.0" requires: - - build-and-test-1.54.0 + - build-and-test-1.69.0 # - lint-check From 187d0b96627774ebd2e80442337f74036afadd4f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 00:10:22 -0400 Subject: [PATCH 018/100] Shuffle Rust versions --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 00993db..9cd54a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -87,16 +87,16 @@ workflows: matrix: parameters: rust-version: - - "1.43.0" + - "1.43.1" - "1.50.0" - "1.54.0" - - "1.69.0" + debian-version: + - "buster" - build-and-test: matrix: parameters: rust-version: - - "1.50.0" - - "1.54.0" + - "1.69.0" rust-features: - "--features='serde'" - "--features='serde,string-only'" From ce8f066f3a2e32c4fe201648c5977b3f41b98536 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 00:12:45 -0400 Subject: [PATCH 019/100] Add new build-and-test-jobs --- .circleci/config.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9cd54a4..8803aab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ jobs: type: string debian-version: type: string - default: "buster" + default: "bullseye" rust-features: type: string default: "" @@ -81,17 +81,22 @@ jobs: workflows: version: 2 - build-and-test: + build-and-test-workflow: jobs: + - lint-check - build-and-test: matrix: parameters: rust-version: - "1.43.1" - - "1.50.0" - "1.54.0" debian-version: - "buster" + + - build-and-test: + name: build-and-test:latest + rust-version: "1.69.0" + - build-and-test: matrix: parameters: @@ -100,12 +105,14 @@ workflows: rust-features: - "--features='serde'" - "--features='serde,string-only'" + - build-and-test: - parameters: - rust-version: "1.69.0" - rust-features: "--no-default-features" + name: build-and-test:no-default-features + rust-version: "1.69.0" + rust-features: "--no-default-features" + - upload-coverage: - rust-version: "1.69.0" - requires: - - build-and-test-1.69.0 - # - lint-check + rust-version: "1.69.0" + requires: + - build-and-test:latest + From 16bdc9ceb1c30925e0249340ebce8b01b2a6cfc4 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 01:24:44 -0400 Subject: [PATCH 020/100] Disable coverage reporting --- .circleci/config.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8803aab..c6ec0fe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: -# codecov: codecov/codecov@3.0.0 +# codecov: codecov/codecov@3.2.4 rust: circleci/rust@1.6.0 jobs: @@ -36,7 +36,7 @@ jobs: key: bigdecimal-cargo-<< parameters.rust-version >>-{{ checksum "Cargo.toml" }} - run: name: Build - command: cargo build + command: cargo build << parameters.rust-features >> - run: name: Test command: cargo test << parameters.rust-features >> @@ -60,7 +60,7 @@ jobs: -e CI=true $(bash <(curl -s https://codecov.io/env)) akubera/rust-codecov:<< parameters.rust-version >>-<< parameters.debian-version >> - sh -c 'cargo test -q && kcov-rust && upload-kcov-results-to-codecov' + sh -c 'cargo test -q --no-run && kcov-rust && upload-kcov-results-to-codecov' - store_artifacts: path: target/cov - store_test_results: @@ -111,8 +111,8 @@ workflows: rust-version: "1.69.0" rust-features: "--no-default-features" - - upload-coverage: - rust-version: "1.69.0" - requires: - - build-and-test:latest + #- upload-coverage: + # rust-version: "1.69.0" + # requires: + # - build-and-test:latest From a2e33bf1a5d3101cbae85d8fa1a3e17dfc0a44ef Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 01:53:42 -0400 Subject: [PATCH 021/100] Disable store_test_results step in upload-coverage job --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c6ec0fe..49be4e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,8 +63,8 @@ jobs: sh -c 'cargo test -q --no-run && kcov-rust && upload-kcov-results-to-codecov' - store_artifacts: path: target/cov - - store_test_results: - path: target/cov + # - store_test_results: + # path: target lint-check: docker: From 61c751329d3c260b96c278171673357d7c2194c5 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 01:54:52 -0400 Subject: [PATCH 022/100] Rename workflow to cargo:build-and-test --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 49be4e7..2611eb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,7 +81,7 @@ jobs: workflows: version: 2 - build-and-test-workflow: + cargo:build-and-test: jobs: - lint-check - build-and-test: From 3e6180695736f7131db530ea5482962f641d0a1d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 20:37:02 -0400 Subject: [PATCH 023/100] Replace badges in README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f60558a..5bad499 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ [![minimum rustc 1.43](https://img.shields.io/badge/rustc-1.43+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) -[![coverage](https://gitlab.com/akubera/bigdecimal-rs/badges/master/coverage.svg)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) -[![build status - master](https://gitlab.com/akubera/bigdecimal-rs/badges/master/pipeline.svg?ignore_skipped=true)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) -[![build status - dev](https://gitlab.com/akubera/bigdecimal-rs/badges/devel/pipeline.svg?ignore_skipped=true)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) +[![codecov](https://codecov.io/gh/akubera/bigdecimal-rs/branch/feature/circleci/graph/badge.svg?token=YTwyxrxJ3S)](https://codecov.io/gh/akubera/bigdecimal-rs) +[![build status - master](https://gitlab.com/akubera/bigdecimal-rs/badges/master/pipeline.svg?ignore_skipped=true&key_text=status:master&key_width=96)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) +[![build status - trunk](https://gitlab.com/akubera/bigdecimal-rs/badges/trunk/pipeline.svg?ignore_skipped=true&key_text=status:trunk&key_width=96)](https://gitlab.com/akubera/bigdecimal-rs/-/pipelines) + Arbitary-precision decimal numbers implemented in pure Rust. From 1798a5ca204e3ce6713aa6e18823dc0ec3c65aeb Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:15:53 -0400 Subject: [PATCH 024/100] Move TryFrom implementation to parsing module --- src/lib.rs | 4 ++- src/parsing.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/parsing.rs diff --git a/src/lib.rs b/src/lib.rs index be41db7..d7de11c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,8 @@ const LOG2_10: f64 = 3.321928094887362_f64; #[macro_use] mod macros; +mod parsing; + #[inline(always)] fn ten_to_the(pow: u64) -> BigInt { if pow < 20 { @@ -1904,7 +1906,7 @@ impl TryFrom for BigDecimal { #[inline] fn try_from(n: f32) -> Result { - BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = ::std::f32::DIGITS as usize)) + parsing::parse_from_f32(n) } } diff --git a/src/parsing.rs b/src/parsing.rs new file mode 100644 index 0000000..ec05316 --- /dev/null +++ b/src/parsing.rs @@ -0,0 +1,78 @@ +//! Routines for parsing values into BigDecimals + +use super::{BigDecimal, ParseBigDecimalError}; + +use num_bigint::{BigInt, BigUint, Sign}; + + +pub(crate) fn parse_from_f32(n: f32) -> Result { + use std::cmp::Ordering::*; + + let bits = n.to_bits(); + if n.is_nan() { + return Err( + ParseBigDecimalError::Other("NAN".into()) + ) + } + + if bits == 0 { + return Ok( + BigDecimal { + int_val: BigInt::new(Sign::NoSign, vec![0]), + scale: 0, + } + ); + } + + let frac = (bits & ((1 << 23) - 1)) + (1 << 23); + let exp = (bits >> 23) & 0xFF; + + let pow = exp as i64 - 127 - 23; + + let sign_bit = bits & (1 << 31); + let sign = if sign_bit == 0 { + Sign::Plus + } else { + Sign::Minus + }; + + let result; + let scale; + match pow.cmp(&0) { + Equal => { + result = BigUint::from(frac); + scale = 0; + } + Less => { + let trailing_zeros = frac.trailing_zeros(); + let reduced_frac = frac >> trailing_zeros; + + let reduced_pow = pow + trailing_zeros as i64; + let shift = BigUint::from(5u8).pow(reduced_pow.abs() as u32); + + result = reduced_frac * shift; + scale = -reduced_pow; + } + Greater => { + let shift = BigUint::from(2u8).pow(pow.abs() as u32); + + result = frac * shift; + scale = 0; + } + } + + return Ok( + BigDecimal { + int_val: BigInt::from_biguint(sign, result), + scale: scale, + } + ); +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod test_parse_from_f32 { + use super::*; + + include!("parsing.tests.parse_from_f32.rs"); +} From 0bf42b13519c273948c53ebc8001a0878b06d58a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:49 -0400 Subject: [PATCH 025/100] Add test cases for parse_from_f32 --- src/parsing.tests.parse_from_f32.rs | 103 ++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/parsing.tests.parse_from_f32.rs diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs new file mode 100644 index 0000000..bfd1901 --- /dev/null +++ b/src/parsing.tests.parse_from_f32.rs @@ -0,0 +1,103 @@ + + +#[test] +fn case_45En1() { + let n = 4.5f32; + let d = parse_from_f32(n); + assert_eq!(d, "4.5".parse().unwrap()); +} + + +#[test] +fn case_15625En5() { + let n = 0.15625f32; + let d = parse_from_f32(n); + assert_eq!(d, "0.15625".parse().unwrap()); +} + + +#[test] +fn case_n15625En5() { + let n = -0.15625f32; + let d = parse_from_f32(n); + assert_eq!(d, "-0.15625".parse().unwrap()); +} + + +#[test] +fn case_n1192092896En7() { + let n = -1.192092896e-07_f32; + let d = parse_from_f32(n); + assert_eq!(d, "-1.1920928955078125E-7".parse().unwrap()); +} + +#[test] +fn case_1401757440() { + let n = 1401757440_f32; + let d = parse_from_f32(n); + assert_eq!(d, "1401757440".parse().unwrap()); +} + +#[test] +fn case_215092En1() { + let n = 21509.2f32; + let d = parse_from_f32(n); + assert_eq!(d, "21509.19921875".parse().unwrap()); +} + +#[test] +fn case_2289620000() { + let n = 2289620000.0f32; + let d = parse_from_f32(n); + assert_eq!(d, "2289619968".parse().unwrap()); +} + +#[test] +fn case_10000000() { + let n = 10000000f32; + let d = parse_from_f32(n); + assert_eq!(d, "10000000".parse().unwrap()); +} + +#[test] +fn case_1en05() { + let n = 1e-05f32; + let d = parse_from_f32(n); + assert_eq!(d, "0.00000999999974737875163555145263671875".parse().unwrap()); +} + +#[test] +fn case_80000197() { + let n = 80000197f32; + let d = parse_from_f32(n); + assert_eq!(d, "80000200".parse().unwrap()); +} + +#[test] +fn case_23283064En16() { + let n = 2.3283064e-10f32; + let d = parse_from_f32(n); + assert_eq!(d, "0.00000000023283064365386962890625".parse().unwrap()); +} + +#[test] +fn case_14693861798803098En17() { + let n = 0.14693861798803098; + let d = parse_from_f32(n); + assert_eq!(d, "0.146938621997833251953125".parse().unwrap()); +} + +#[test] +fn case_nan() { + let n = f32::NAN; + let d = parse_from_f32(n); + assert_eq!(d, "510423550381407695195061911147652317184".parse().unwrap()); +} + + +#[test] +fn case_try_from_nan() { + let n = f32::NAN; + let d = try_parse_from_f32(n); + assert!(d.is_err()); +} From 6b7bbafe79e0e2e8dc2720b917f9867cec254e0c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:13 -0400 Subject: [PATCH 026/100] Add paste dev dependency --- Cargo.toml | 3 +++ src/lib.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index e9db4f7..6c31be1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ num-integer = "0.1" num-traits = "0.2" serde = { version = "1.0", optional = true } +[dev-dependencies] +paste = "*" + [dev-dependencies.serde_json] version = "1.0" diff --git a/src/lib.rs b/src/lib.rs index d7de11c..0429b0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,9 @@ const LOG2_10: f64 = 3.321928094887362_f64; #[macro_use] mod macros; +#[cfg(test)] +extern crate paste; + mod parsing; #[inline(always)] From 9c064a21649c3a789a2028855be698a085ec69ed Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:14 -0400 Subject: [PATCH 027/100] Rewrite parse_from_f32 tests with macros --- src/parsing.tests.parse_from_f32.rs | 134 ++++++++++++++-------------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index bfd1901..8a85b33 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -1,103 +1,99 @@ - - -#[test] -fn case_45En1() { - let n = 4.5f32; - let d = parse_from_f32(n); - assert_eq!(d, "4.5".parse().unwrap()); -} - - -#[test] -fn case_15625En5() { - let n = 0.15625f32; - let d = parse_from_f32(n); - assert_eq!(d, "0.15625".parse().unwrap()); +// tests for function bigdecimal::parsing::parse_from_f23 + +use paste::paste; + +use std::f32; + +macro_rules! impl_test { + ($name:ident : $input:literal == $expected:literal) => { + paste! { + #[test] + fn [< case $name >]() { + let n = $input; + let d = parse_from_f32(n); + assert_eq!(d, $expected.parse().unwrap()); + } + + #[test] + fn [< case_neg $name >]() { + let n = -$input; + let d = parse_from_f32(n); + assert_eq!(d, concat!("-", $expected).parse().unwrap()); + } + } + } } -#[test] -fn case_n15625En5() { - let n = -0.15625f32; - let d = parse_from_f32(n); - assert_eq!(d, "-0.15625".parse().unwrap()); -} - - -#[test] -fn case_n1192092896En7() { - let n = -1.192092896e-07_f32; - let d = parse_from_f32(n); - assert_eq!(d, "-1.1920928955078125E-7".parse().unwrap()); -} - -#[test] -fn case_1401757440() { - let n = 1401757440_f32; - let d = parse_from_f32(n); - assert_eq!(d, "1401757440".parse().unwrap()); -} +impl_test!(_45En1 : 4.5 == "4.5"); +impl_test!(_15625En5 : 0.15625 == "0.15625"); +impl_test!(_1192092896En7 : 1.192092896e-7 == "1.1920928955078125E-7"); +impl_test!(_1401757440 : 1401757440. == "1401757440"); +impl_test!(_215092En1 : 21509.2 == "21509.19921875"); +impl_test!(_2289620000 : 2289620000.0 == "2289619968"); +impl_test!(_10000000 : 10000000. == "10000000"); +impl_test!(_1en05 : 1e-5 == "0.00000999999974737875163555145263671875"); +impl_test!(_1en1 : 1e-1 == "0.100000001490116119384765625"); +impl_test!(_80000197 : 80000197e0 == "80000200"); +impl_test!(_23283064En16 : 2.3283064e-10 == "0.00000000023283064365386962890625"); +impl_test!(_14693861798803098En17 : 0.14693861798803098 == "0.146938621997833251953125"); +impl_test!(_1e20 : 1e20 == "100000002004087734272"); +impl_test!(_1e30 : 1e30 == "1000000015047466219876688855040"); +impl_test!(_1e38 : 1e38 == "99999996802856924650656260769173209088"); +impl_test!(_317e36 : 317e36 == "317000006395220278118691742155288870912"); +impl_test!(_23509889819en48 : 2.3509889819e-38 == "2.35098898190426788090088725919040801362055736959656341832065776397049129686767088287524529732763767242431640625E-38"); +impl_test!(_235098744048en49 : 2.35098744048e-38 == "2.350987440475957123602109243087866394712812961308427354153308831195379018097479928428583662025630474090576171875E-38"); +impl_test!(_6_99999952316 : 6.99999952316 == "6.999999523162841796875"); +impl_test!(_317en40 : 317e-40 == "3.1700000098946435501119816090716154772221806896649747100732700841687651538425285480116144753992557525634765625E-38"); -#[test] -fn case_215092En1() { - let n = 21509.2f32; - let d = parse_from_f32(n); - assert_eq!(d, "21509.19921875".parse().unwrap()); -} #[test] -fn case_2289620000() { - let n = 2289620000.0f32; +fn case_f32_min() { + let n = std::f32::MIN; let d = parse_from_f32(n); - assert_eq!(d, "2289619968".parse().unwrap()); + assert_eq!(d, "-340282346638528859811704183484516925440".parse().unwrap()); } #[test] -fn case_10000000() { - let n = 10000000f32; +fn case_f32_max() { + let n = std::f32::MAX; let d = parse_from_f32(n); - assert_eq!(d, "10000000".parse().unwrap()); + assert_eq!(d, "340282346638528859811704183484516925440".parse().unwrap()); } #[test] -fn case_1en05() { - let n = 1e-05f32; +fn case_f32_epsilon() { + let n = std::f32::EPSILON; let d = parse_from_f32(n); - assert_eq!(d, "0.00000999999974737875163555145263671875".parse().unwrap()); + assert_eq!(d, "1.1920928955078125E-7".parse().unwrap()); } #[test] -fn case_80000197() { - let n = 80000197f32; +fn case_f32_pi() { + let n = std::f32::consts::PI; let d = parse_from_f32(n); - assert_eq!(d, "80000200".parse().unwrap()); + assert_eq!(d, "3.1415927410125732421875".parse().unwrap()); } #[test] -fn case_23283064En16() { - let n = 2.3283064e-10f32; - let d = parse_from_f32(n); - assert_eq!(d, "0.00000000023283064365386962890625".parse().unwrap()); -} +fn case_nan() { + let n = f32::from_bits(0b01111111110000000000000000000000); + assert!(n.is_nan()); -#[test] -fn case_14693861798803098En17() { - let n = 0.14693861798803098; let d = parse_from_f32(n); - assert_eq!(d, "0.146938621997833251953125".parse().unwrap()); + assert_eq!(d, "510423550381407695195061911147652317184".parse().unwrap()); } #[test] -fn case_nan() { +fn case_try_from_nan() { let n = f32::NAN; - let d = parse_from_f32(n); - assert_eq!(d, "510423550381407695195061911147652317184".parse().unwrap()); + let d = try_parse_from_f32(n); + assert!(d.is_err()); } - #[test] -fn case_try_from_nan() { - let n = f32::NAN; +fn case_try_from_infinity() { + let n = f32::INFINITY; let d = try_parse_from_f32(n); assert!(d.is_err()); } From efddbd2fb1cab7c2f7e070399d1816ea497690e0 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:29 -0400 Subject: [PATCH 028/100] Split try_parse_from_f32 behavior from parse_from_f32 --- src/parsing.rs | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/parsing.rs b/src/parsing.rs index ec05316..0b2fd1d 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -5,23 +5,35 @@ use super::{BigDecimal, ParseBigDecimalError}; use num_bigint::{BigInt, BigUint, Sign}; -pub(crate) fn parse_from_f32(n: f32) -> Result { +/// Try creating bigdecimal from f32 +/// +/// Non "normal" values will return Error case +/// +pub(crate) fn try_parse_from_f32(n: f32) -> Result { + use std::num::FpCategory::*; + match n.classify() { + Nan => Err(ParseBigDecimalError::Other("NAN".into())), + Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), + Subnormal => Err(ParseBigDecimalError::Other("Subnormal".into())), + Normal | Zero => Ok(parse_from_f32(n)), + } +} + + +/// Create bigdecimal from f32 +/// +/// Non "normal" values is undefined behavior +/// +pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { use std::cmp::Ordering::*; let bits = n.to_bits(); - if n.is_nan() { - return Err( - ParseBigDecimalError::Other("NAN".into()) - ) - } if bits == 0 { - return Ok( - BigDecimal { - int_val: BigInt::new(Sign::NoSign, vec![0]), - scale: 0, - } - ); + return BigDecimal { + int_val: BigInt::new(Sign::NoSign, vec![0]), + scale: 0, + }; } let frac = (bits & ((1 << 23) - 1)) + (1 << 23); @@ -61,12 +73,10 @@ pub(crate) fn parse_from_f32(n: f32) -> Result } } - return Ok( - BigDecimal { - int_val: BigInt::from_biguint(sign, result), - scale: scale, - } - ); + BigDecimal { + int_val: BigInt::from_biguint(sign, result), + scale: scale, + } } #[cfg(test)] From 8ae0f5763fb1fa3b376859032ad5bd3eda63c6d7 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:30 -0400 Subject: [PATCH 029/100] Revert serde_json dependency to 0.9 --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c31be1..115585a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,7 @@ serde = { version = "1.0", optional = true } [dev-dependencies] paste = "*" - -[dev-dependencies.serde_json] -version = "1.0" +serde_json = { version = "0.9" } [features] string-only = [] From 6d5f33bbc980538aa908268c5fbefeea20bac662 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:35 -0400 Subject: [PATCH 030/100] Use try_parse_from_f32 in try_from:: and update tests --- src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0429b0b..4fdc8d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1909,7 +1909,7 @@ impl TryFrom for BigDecimal { #[inline] fn try_from(n: f32) -> Result { - parsing::parse_from_f32(n) + parsing::try_parse_from_f32(n) } } @@ -2137,7 +2137,7 @@ mod bigdecimal_tests { BigDecimal::from_f32(0.001).unwrap(), ]; - let expected_sum = BigDecimal::from_f32(2.801).unwrap(); + let expected_sum = BigDecimal::from_f32(2.8009998798370361328125).unwrap(); let sum = vals.iter().sum::(); assert_eq!(expected_sum, sum); @@ -2151,7 +2151,8 @@ mod bigdecimal_tests { // BigDecimal::from_f32(0.001).unwrap(), ]; - let expected_sum = BigDecimal::from_f32(0.3).unwrap(); + // let expected_sum = BigDecimal::from_f32(0.3).unwrap(); + let expected_sum = BigDecimal::from_f32(0.300000011920928955078125).unwrap(); let sum = vals.iter().sum::(); assert_eq!(expected_sum, sum); @@ -2251,9 +2252,9 @@ mod bigdecimal_tests { ("0.25", 0.25), ("50.", 50.0), ("50000", 50000.), - ("0.001", 0.001), - ("12.34", 12.34), - ("0.15625", 5.0 * 0.03125), + ("0.001000000047497451305389404296875", 0.001), + ("12.340000152587890625", 12.34), + ("0.15625", 0.15625), ("3.141593", ::std::f32::consts::PI), ("31415.93", ::std::f32::consts::PI * 10000.0), ("94247.78", ::std::f32::consts::PI * 30000.0), From e9a51bd02eed46d2b653a608c2c4c66ba562466c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:36 -0400 Subject: [PATCH 031/100] Fix expected values in bigdecimal_tests::sum tests --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4fdc8d4..f9ab1f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2137,7 +2137,7 @@ mod bigdecimal_tests { BigDecimal::from_f32(0.001).unwrap(), ]; - let expected_sum = BigDecimal::from_f32(2.8009998798370361328125).unwrap(); + let expected_sum = BigDecimal::from_str("2.80100001196842640638351440429688").unwrap(); let sum = vals.iter().sum::(); assert_eq!(expected_sum, sum); @@ -2148,11 +2148,9 @@ mod bigdecimal_tests { let vals = vec![ BigDecimal::from_f32(0.1).unwrap(), BigDecimal::from_f32(0.2).unwrap(), - // BigDecimal::from_f32(0.001).unwrap(), ]; - // let expected_sum = BigDecimal::from_f32(0.3).unwrap(); - let expected_sum = BigDecimal::from_f32(0.300000011920928955078125).unwrap(); + let expected_sum = BigDecimal::from_str("0.300000004470348358154296875").unwrap(); let sum = vals.iter().sum::(); assert_eq!(expected_sum, sum); From 5057da6ae963160b1f9658a8dcfae3d73cf105cd Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:17:37 -0400 Subject: [PATCH 032/100] Factor f32 parsing into split_f32_into_parts --- src/parsing.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/parsing.rs b/src/parsing.rs index 0b2fd1d..940dcdf 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -20,6 +20,30 @@ pub(crate) fn try_parse_from_f32(n: f32) -> Result (u32, i64, Sign) { + let bits = f.to_bits(); + let frac = (bits & ((1 << 23) - 1)) + (1 << 23); + let exp = (bits >> 23) & 0xFF; + + let pow = exp as i64 - 127 - 23; + + let sign_bit = bits & (1 << 31); + let sign = if sign_bit == 0 { + Sign::Plus + } else { + Sign::Minus + }; + + (frac, pow, sign) +} + + /// Create bigdecimal from f32 /// /// Non "normal" values is undefined behavior @@ -36,17 +60,8 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { }; } - let frac = (bits & ((1 << 23) - 1)) + (1 << 23); - let exp = (bits >> 23) & 0xFF; - - let pow = exp as i64 - 127 - 23; - - let sign_bit = bits & (1 << 31); - let sign = if sign_bit == 0 { - Sign::Plus - } else { - Sign::Minus - }; + // n = frac * 2^pow + let (frac, pow, sign) = split_f32_into_parts(n); let result; let scale; From 57c90ee5bd93b1fa4dff45d44da37dc3f11fb215 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 01:51:31 -0400 Subject: [PATCH 033/100] Fix case where power could be positive when calculating shift --- src/parsing.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parsing.rs b/src/parsing.rs index 940dcdf..3997e46 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -71,11 +71,13 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { scale = 0; } Less => { - let trailing_zeros = frac.trailing_zeros(); - let reduced_frac = frac >> trailing_zeros; + let trailing_zeros = std::cmp::min(frac.trailing_zeros(), -pow as u32); + let reduced_frac = frac >> trailing_zeros; let reduced_pow = pow + trailing_zeros as i64; - let shift = BigUint::from(5u8).pow(reduced_pow.abs() as u32); + debug_assert!(reduced_pow <= 0); + + let shift = BigUint::from(5u8).pow(-reduced_pow as u32); result = reduced_frac * shift; scale = -reduced_pow; From f3d56c202536dbafffea0517891dca007f0d979d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 01:55:55 -0400 Subject: [PATCH 034/100] Add more tests for parse_from_f32 --- src/parsing.tests.parse_from_f32.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index 8a85b33..16ed80c 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -9,22 +9,29 @@ macro_rules! impl_test { paste! { #[test] fn [< case $name >]() { - let n = $input; + let n = $input as f32; let d = parse_from_f32(n); assert_eq!(d, $expected.parse().unwrap()); } #[test] fn [< case_neg $name >]() { - let n = -$input; + let n = -($input as f32); let d = parse_from_f32(n); assert_eq!(d, concat!("-", $expected).parse().unwrap()); } } - } + }; } +impl_test!(_1 : 1.0 == "1"); +impl_test!(_5en1 : 0.5 == "0.5"); +impl_test!(_25en2 : 0.25 == "0.25"); +impl_test!(_50 : 50. == "50"); +impl_test!(_1en3 : 0.001 == "0.001000000047497451305389404296875"); +impl_test!(_033203125en8 : 0.033203125 == "0.033203125"); + impl_test!(_45En1 : 4.5 == "4.5"); impl_test!(_15625En5 : 0.15625 == "0.15625"); impl_test!(_1192092896En7 : 1.192092896e-7 == "1.1920928955078125E-7"); @@ -34,6 +41,7 @@ impl_test!(_2289620000 : 2289620000.0 == "2289619968"); impl_test!(_10000000 : 10000000. == "10000000"); impl_test!(_1en05 : 1e-5 == "0.00000999999974737875163555145263671875"); impl_test!(_1en1 : 1e-1 == "0.100000001490116119384765625"); +impl_test!(_2en1 : 2e-1 == "0.20000000298023223876953125"); impl_test!(_80000197 : 80000197e0 == "80000200"); impl_test!(_23283064En16 : 2.3283064e-10 == "0.00000000023283064365386962890625"); impl_test!(_14693861798803098En17 : 0.14693861798803098 == "0.146938621997833251953125"); @@ -45,6 +53,8 @@ impl_test!(_23509889819en48 : 2.3509889819e-38 == "2.350988981904267880900887259 impl_test!(_235098744048en49 : 2.35098744048e-38 == "2.350987440475957123602109243087866394712812961308427354153308831195379018097479928428583662025630474090576171875E-38"); impl_test!(_6_99999952316 : 6.99999952316 == "6.999999523162841796875"); impl_test!(_317en40 : 317e-40 == "3.1700000098946435501119816090716154772221806896649747100732700841687651538425285480116144753992557525634765625E-38"); +impl_test!(_4294967295 : 4294967295. == "4294967296"); +impl_test!(_158456325029e18 : 1.58456325029e+29 == "158456325028528675187087900672"); #[test] From 5824b8462796f2fd81375133825af34e8b7ced85 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 02:10:45 -0400 Subject: [PATCH 035/100] Fix and add cases to test_from_f32 --- src/lib.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f9ab1f6..c678cc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2137,7 +2137,7 @@ mod bigdecimal_tests { BigDecimal::from_f32(0.001).unwrap(), ]; - let expected_sum = BigDecimal::from_str("2.80100001196842640638351440429688").unwrap(); + let expected_sum = BigDecimal::from_str("2.801000011968426406383514404296875").unwrap(); let sum = vals.iter().sum::(); assert_eq!(expected_sum, sum); @@ -2245,6 +2245,7 @@ mod bigdecimal_tests { #[test] fn test_from_f32() { let vals = vec![ + ("0.0", 0.0), ("1.0", 1.0), ("0.5", 0.5), ("0.25", 0.25), @@ -2253,20 +2254,18 @@ mod bigdecimal_tests { ("0.001000000047497451305389404296875", 0.001), ("12.340000152587890625", 12.34), ("0.15625", 0.15625), - ("3.141593", ::std::f32::consts::PI), - ("31415.93", ::std::f32::consts::PI * 10000.0), - ("94247.78", ::std::f32::consts::PI * 30000.0), - // ("3.14159265358979323846264338327950288f32", ::std::f32::consts::PI), - + ("3.1415927410125732421875", ::std::f32::consts::PI), + ("31415.927734375", ::std::f32::consts::PI * 10000.0), + ("94247.78125", ::std::f32::consts::PI * 30000.0), + ("1048576", 1048576.), ]; for (s, n) in vals { let expected = BigDecimal::from_str(s).unwrap(); let value = BigDecimal::from_f32(n).unwrap(); assert_eq!(expected, value); - // assert_eq!(expected, n); } - } + #[test] fn test_from_f64() { let vals = vec![ From 6f26bdef3c0cc7a5005027eb260476dd8b98c579 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 19:43:56 -0400 Subject: [PATCH 036/100] Add parse_from_f64 tests --- src/parsing.tests.parse_from_f32.rs | 2 +- src/parsing.tests.parse_from_f64.rs | 102 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/parsing.tests.parse_from_f64.rs diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index 16ed80c..89b6727 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -1,4 +1,4 @@ -// tests for function bigdecimal::parsing::parse_from_f23 +// tests for function bigdecimal::parsing::parse_from_f32 use paste::paste; diff --git a/src/parsing.tests.parse_from_f64.rs b/src/parsing.tests.parse_from_f64.rs new file mode 100644 index 0000000..04b14e1 --- /dev/null +++ b/src/parsing.tests.parse_from_f64.rs @@ -0,0 +1,102 @@ +// tests for function bigdecimal::parsing::parse_from_f64 + +use paste::paste; + +use std::f64; + +macro_rules! impl_test { + ($input:literal == $expected:literal) => { + paste! { impl_test!( [< "_" $input >] : $input == $expected); } + }; + ($name:ident : bits:$input:literal => $expected:literal) => { + impl_test!($name : f64::from_bits($input) => $expected); + }; + ($name:ident : $input:literal == $expected:literal) => { + impl_test!($name : ($input as f64) => $expected); + }; + ($name:ident : $input:expr => $expected:literal) => { + paste! { + #[test] + fn [< case $name >]() { + let n = $input; + let d = parse_from_f64(n); + assert_eq!(d, $expected.parse().unwrap()); + } + + #[test] + fn [< case_neg $name >]() { + let n = f64::from_bits($input.to_bits() | (1<<63)); + let d = parse_from_f64(n); + assert_eq!(d, concat!("-", $expected).parse().unwrap()); + } + } + }; +} + +impl_test!(_1 : 1.0 == "1"); +impl_test!(_2 : 2.0 == "2"); +impl_test!(_3 : 3.0 == "3"); +impl_test!(_5en1 : 0.5 == "0.5"); +impl_test!(_25en2 : 0.25 == "0.25"); +impl_test!(_1en1 : 0.1 == "0.1000000000000000055511151231257827021181583404541015625"); +impl_test!(_1over3 : 0.333333333333333333333333333333 == "0.333333333333333314829616256247390992939472198486328125"); +impl_test!(_pi : 3.141592653589793 == "3.141592653589793115997963468544185161590576171875"); +impl_test!(_near_3 : 3.0000000000000004 == "3.000000000000000444089209850062616169452667236328125"); +impl_test!(_8eneg306 : 8.544283616667655e-306 == "8.5442836166676545758745469881475846986178991076220674838778719735182619591847930738097459423424470941335996703553180065389909675214026779902482660710563190540056652827644969523715287333767167538014707594736533997824798692690142890189753467148541192574394234161821394612038920127719106177776787375705338074667624093006332620080979623387970617655687653904110103913103933178304212511707769987213793880764157458662751217010283883439888757033430556011326632895537144105152597427684695380215955244686097497705226475608085097617996058799189036784865947060736971859470127760066696392182317083388979882704968230500619384728741377732016919538675848783600526390429792978252568964346334556191024880163233082812954995600973750951114861484914086986464099027216434478759765625e-306"); +impl_test!(_8e306 : 3e300 == "3000000000000000157514280765613260746113405743324477464747562346535407373966724587359114125241343592131113331498651634530827569706081291726934376554360120948545161602779727411213490701384364270178106859704912399835243357116902922640223958228340427483737776366460170528514347008416589160596378201620480"); + +impl_test!(_50 : 50. == "50"); +impl_test!(_nanbits : bits:0b_0_11111111111_1000000000000000000000000000000000000000000000000001 => "269653970229347426076201969312749943170150807578117307259170330445749843759196293443300553362892619730839480672521111823337121537071529813188030913831084401350087805833926634314566788423582671529934053315387252306324360914392174188827078768228648633522131134987762597502339006422840407304422939101316534763520"); +impl_test!(_3105036184601418e246 : bits:0b_0_11100000000_0000000000000000000000000000000000000000000000000000 => "3105036184601417870297958976925005110513772034233393222278104076052101905372753772661756817657292955900975461394262146412343160088229628782888574550082362278408909952041699811100530571263196889650525998387432937501785693707632115712"); + + +#[test] +fn case_f64_min() { + let n = std::f64::MIN; + let d = parse_from_f64(n); + assert_eq!(d, "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368".parse().unwrap()); +} + +#[test] +fn case_f64_max() { + let n = std::f64::MAX; + let d = parse_from_f64(n); + assert_eq!(d, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368".parse().unwrap()); +} + +#[test] +fn case_f64_epsilon() { + let n = std::f64::EPSILON; + let d = parse_from_f64(n); + assert_eq!(d, "2.220446049250313080847263336181640625e-16".parse().unwrap()); +} + +#[test] +fn case_f64_pi() { + let n = std::f64::consts::PI; + let d = parse_from_f64(n); + assert_eq!(d, "3.141592653589793115997963468544185161590576171875".parse().unwrap()); +} + +#[test] +fn case_nan() { + let n = f64::from_bits(0b0_11111111111_1000000000000000000000000000000000000000000000000000); + assert!(n.is_nan()); + + let d = parse_from_f64(n); + assert_eq!(d, "269653970229347386159395778618353710042696546841345985910145121736599013708251444699062715983611304031680170819807090036488184653221624933739271145959211186566651840137298227914453329401869141179179624428127508653257226023513694322210869665811240855745025766026879447359920868907719574457253034494436336205824".parse().unwrap()); +} + +#[test] +fn case_try_from_nan() { + let n = f64::NAN; + let d = try_parse_from_f64(n); + assert!(d.is_err()); +} + +#[test] +fn case_try_from_infinity() { + let n = f64::INFINITY; + let d = try_parse_from_f64(n); + assert!(d.is_err()); +} From d82ab14d5c646388b70a55edc15b2a1d259a890f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 19:45:26 -0400 Subject: [PATCH 037/100] Add parse_from_f64 to parsing.rs --- src/parsing.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/parsing.rs b/src/parsing.rs index 3997e46..91c5e53 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -103,3 +103,103 @@ mod test_parse_from_f32 { include!("parsing.tests.parse_from_f32.rs"); } + + +/// Try creating bigdecimal from f64 +/// +/// Non "normal" values will return Error case +/// +pub(crate) fn try_parse_from_f64(n: f64) -> Result { + use std::num::FpCategory::*; + match n.classify() { + Nan => Err(ParseBigDecimalError::Other("NAN".into())), + Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), + Subnormal => Err(ParseBigDecimalError::Other("Subnormal".into())), + Normal | Zero => Ok(parse_from_f64(n)), + } +} + + +/// Return mantissa, exponent, and sign of given floating point number +/// +/// ```math +/// f = frac * 2^pow +/// ``` +/// +fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) { + let bits = f.to_bits(); + let frac = (bits & ((1 << 52) - 1)) + (1 << 52); + let exp = (bits >> 52) & 0x7FF; + + let pow = exp as i64 - 1023 - 52; + + let sign_bit = bits & (1 << 63); + let sign = if sign_bit == 0 { + Sign::Plus + } else { + Sign::Minus + }; + + (frac, pow, sign) +} + + +/// Create bigdecimal from f64 +/// +/// Non "normal" values is undefined behavior +/// +pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { + use std::cmp::Ordering::*; + + let bits = n.to_bits(); + + if bits == 0 { + return BigDecimal { + int_val: BigInt::new(Sign::NoSign, vec![0]), + scale: 0, + }; + } + + // n = frac * 2^pow + let (frac, pow, sign) = split_f64_into_parts(n); + debug_assert!(frac > 0); + + let result; + let scale; + match pow.cmp(&0) { + Equal => { + result = BigUint::from(frac); + scale = 0; + } + Less => { + let trailing_zeros = std::cmp::min(frac.trailing_zeros(), -pow as u32); + + let reduced_frac = frac >> trailing_zeros; + let reduced_pow = pow + trailing_zeros as i64; + debug_assert!(reduced_pow <= 0); + + let shift = BigUint::from(5u8).pow(-reduced_pow as u32); + + result = reduced_frac * shift; + scale = -reduced_pow; + } + Greater => { + let shift = BigUint::from(2u8).pow(pow as u32); + result = frac * shift; + scale = 0; + } + } + + BigDecimal { + int_val: BigInt::from_biguint(sign, result), + scale: scale, + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod test_parse_from_f64 { + use super::*; + + include!("parsing.tests.parse_from_f64.rs"); +} From 4739e145cbfa573bf6a6b8e2380e8dee0f676e3a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:10:33 -0400 Subject: [PATCH 038/100] Fix parsing negative zero floating point values --- src/parsing.rs | 5 +++-- src/parsing.tests.parse_from_f32.rs | 1 + src/parsing.tests.parse_from_f64.rs | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/parsing.rs b/src/parsing.rs index 91c5e53..7337e30 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -53,7 +53,7 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { let bits = n.to_bits(); - if bits == 0 { + if (bits << 1) == 0 { return BigDecimal { int_val: BigInt::new(Sign::NoSign, vec![0]), scale: 0, @@ -153,7 +153,8 @@ pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { let bits = n.to_bits(); - if bits == 0 { + // shift right by 1 bit to handle -0.0 + if (bits << 1) == 0 { return BigDecimal { int_val: BigInt::new(Sign::NoSign, vec![0]), scale: 0, diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index 89b6727..fb9474c 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -25,6 +25,7 @@ macro_rules! impl_test { } +impl_test!(_0 : 0.0 == "0"); impl_test!(_1 : 1.0 == "1"); impl_test!(_5en1 : 0.5 == "0.5"); impl_test!(_25en2 : 0.25 == "0.25"); diff --git a/src/parsing.tests.parse_from_f64.rs b/src/parsing.tests.parse_from_f64.rs index 04b14e1..3e88de3 100644 --- a/src/parsing.tests.parse_from_f64.rs +++ b/src/parsing.tests.parse_from_f64.rs @@ -33,6 +33,7 @@ macro_rules! impl_test { }; } +impl_test!(_0 : 0.0 == "0"); impl_test!(_1 : 1.0 == "1"); impl_test!(_2 : 2.0 == "2"); impl_test!(_3 : 3.0 == "3"); From 177c0913c88090132465af2a2875badf3ba85568 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 20 May 2023 20:14:21 -0400 Subject: [PATCH 039/100] Use parse_from_f64 in BigDecimal::try_from --- src/lib.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c678cc3..0565f5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1918,7 +1918,7 @@ impl TryFrom for BigDecimal { #[inline] fn try_from(n: f64) -> Result { - BigDecimal::from_str(&format!("{:.PRECISION$e}", n, PRECISION = ::std::f64::DIGITS as usize)) + parsing::try_parse_from_f64(n) } } @@ -2273,15 +2273,14 @@ mod bigdecimal_tests { ("0.5", 0.5), ("50", 50.), ("50000", 50000.), - ("1e-3", 0.001), + ("0.001000000000000000020816681711721685132943093776702880859375", 0.001), ("0.25", 0.25), - ("12.34", 12.34), - // ("12.3399999999999999", 12.34), // <- Precision 16 decimal points + ("12.339999999999999857891452847979962825775146484375", 12.34), ("0.15625", 5.0 * 0.03125), - ("0.3333333333333333", 1.0 / 3.0), - ("3.141592653589793", ::std::f64::consts::PI), - ("31415.92653589793", ::std::f64::consts::PI * 10000.0f64), - ("94247.77960769380", ::std::f64::consts::PI * 30000.0f64), + ("0.333333333333333314829616256247390992939472198486328125", 1.0 / 3.0), + ("3.141592653589793115997963468544185161590576171875", ::std::f64::consts::PI), + ("31415.926535897931898944079875946044921875", ::std::f64::consts::PI * 10000.0f64), + ("94247.779607693795696832239627838134765625", ::std::f64::consts::PI * 30000.0f64), ]; for (s, n) in vals { let expected = BigDecimal::from_str(s).unwrap(); From d47d366cbe572e831fde9d74871ebc922a91ecea Mon Sep 17 00:00:00 2001 From: Big Herc Date: Fri, 24 Feb 2023 01:45:06 +0100 Subject: [PATCH 040/100] Feature: add support for `no_std` environments --- Cargo.toml | 26 ++++++++---- src/lib.rs | 99 ++++++++++++++++++++++++++++------------------ src/with_alloc.rs | 3 ++ src/with_std.rs | 4 ++ src/without_std.rs | 11 ++++++ 5 files changed, 97 insertions(+), 46 deletions(-) create mode 100644 src/with_alloc.rs create mode 100644 src/with_std.rs create mode 100644 src/without_std.rs diff --git a/Cargo.toml b/Cargo.toml index 115585a..d0392e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,18 +6,30 @@ description = "Arbitrary precision decimal numbers" documentation = "https://docs.rs/bigdecimal" homepage = "https://github.com/akubera/bigdecimal-rs" repository = "https://github.com/akubera/bigdecimal-rs" -keywords = ["mathematics", "numerics", "decimal", "arbitrary-precision", "floating-point"] +keywords = [ + "mathematics", + "numerics", + "decimal", + "arbitrary-precision", + "floating-point", + "no-std", +] license = "MIT/Apache-2.0" [dependencies] -num-bigint = "0.4" -num-integer = "0.1" -num-traits = "0.2" -serde = { version = "1.0", optional = true } +libm = "0.2.6" +num-bigint = { version = "0.4", default-features = false } +num-integer = { version = "0.1", default-features = false } +num-traits = { version = "0.2", default-features = false } +serde = { version = "1.0", optional = true, default-features = false } [dev-dependencies] -paste = "*" -serde_json = { version = "0.9" } +paste = "1" +serde_json = "1.0" +siphasher = { version = "0.3.10", default-features = false } [features] +default = ["std"] +alloc = [] string-only = [] +std = ["num-bigint/std", "num-integer/std", "num-traits/std", "serde/std"] diff --git a/src/lib.rs b/src/lib.rs index 0565f5b..cb6e198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ //! //! println!("Input ({}) with 10 decimals: {} vs {})", input, dec, float); //! ``` +#![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unreadable_literal)] #![allow(clippy::needless_return)] #![allow(clippy::suspicious_arithmetic_impl)] @@ -53,16 +54,24 @@ extern crate num_integer; #[cfg(feature = "serde")] extern crate serde; -use std::cmp::Ordering; -use std::convert::TryFrom; -use std::default::Default; -use std::error::Error; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::num::{ParseFloatError, ParseIntError}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; -use std::iter::Sum; -use std::str::{self, FromStr}; +#[cfg(feature = "std")] +include!("./with_std.rs"); + +#[cfg(not(feature = "std"))] +include!("./without_std.rs"); + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +include!("./with_alloc.rs"); + +use crate::cmp::Ordering; +use crate::convert::TryFrom; +use crate::default::Default; +use crate::hash::{Hash, Hasher}; +use crate::num::{ParseFloatError, ParseIntError}; +use crate::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use crate::iter::Sum; +use crate::str::FromStr; +use crate::string::{String, ToString}; use num_bigint::{BigInt, ParseBigIntError, Sign, ToBigInt}; use num_integer::Integer as IntegerTrait; @@ -159,6 +168,17 @@ pub struct BigDecimal { scale: i64, } +#[cfg(not(feature = "std"))] +// f64::exp2 is only available in std, we have to use an external crate like libm +fn exp2(x: f64) -> f64 { + libm::exp2(x) +} + +#[cfg(feature = "std")] +fn exp2(x: f64) -> f64 { + x.exp2() +} + impl BigDecimal { /// Creates and initializes a `BigDecimal`. /// @@ -412,7 +432,8 @@ impl BigDecimal { let guess = { let magic_guess_scale = 1.1951678538495576_f64; let initial_guess = (self.int_val.bits() as f64 - self.scale as f64 * LOG2_10) / 2.0; - let res = magic_guess_scale * initial_guess.exp2(); + let res = magic_guess_scale * exp2(initial_guess); + if res.is_normal() { BigDecimal::try_from(res).unwrap() } else { @@ -484,7 +505,8 @@ impl BigDecimal { let guess = { let magic_guess_scale = 1.124960491619939_f64; let initial_guess = (self.int_val.bits() as f64 - self.scale as f64 * LOG2_10) / 3.0; - let res = magic_guess_scale * initial_guess.exp2(); + let res = magic_guess_scale * exp2(initial_guess); + if res.is_normal() { BigDecimal::try_from(res).unwrap() } else { @@ -551,7 +573,7 @@ impl BigDecimal { let magic_factor = 0.721507597259061_f64; let initial_guess = scale * LOG2_10 - bits; - let res = magic_factor * initial_guess.exp2(); + let res = magic_factor * exp2(initial_guess); if res.is_normal() { BigDecimal::try_from(res).unwrap() @@ -702,7 +724,8 @@ impl fmt::Display for ParseBigDecimalError { } } -impl Error for ParseBigDecimalError { +#[cfg(feature = "std")] +impl std::error::Error for ParseBigDecimalError { fn description(&self) -> &str { "failed to parse bigint/biguint" } @@ -1018,7 +1041,7 @@ impl Sub for BigDecimal { #[inline] fn sub(self, rhs: BigDecimal) -> BigDecimal { let mut lhs = self; - let scale = std::cmp::max(lhs.scale, rhs.scale); + let scale = cmp::max(lhs.scale, rhs.scale); match lhs.scale.cmp(&rhs.scale) { Ordering::Equal => { @@ -1037,7 +1060,7 @@ impl<'a> Sub<&'a BigDecimal> for BigDecimal { #[inline] fn sub(self, rhs: &BigDecimal) -> BigDecimal { let mut lhs = self; - let scale = std::cmp::max(lhs.scale, rhs.scale); + let scale = cmp::max(lhs.scale, rhs.scale); match lhs.scale.cmp(&rhs.scale) { Ordering::Equal => { @@ -1535,7 +1558,7 @@ impl Rem for BigDecimal { #[inline] fn rem(self, other: BigDecimal) -> BigDecimal { - let scale = std::cmp::max(self.scale, other.scale); + let scale = cmp::max(self.scale, other.scale); let num = self.take_and_scale(scale).int_val; let den = other.take_and_scale(scale).int_val; @@ -1549,7 +1572,7 @@ impl<'a> Rem<&'a BigDecimal> for BigDecimal { #[inline] fn rem(self, other: &BigDecimal) -> BigDecimal { - let scale = std::cmp::max(self.scale, other.scale); + let scale = cmp::max(self.scale, other.scale); let num = self.take_and_scale(scale).int_val; let den = &other.int_val; @@ -1566,7 +1589,7 @@ impl<'a> Rem for &'a BigDecimal { #[inline] fn rem(self, other: BigDecimal) -> BigDecimal { - let scale = std::cmp::max(self.scale, other.scale); + let scale = cmp::max(self.scale, other.scale); let num = &self.int_val; let den = other.take_and_scale(scale).int_val; @@ -1586,7 +1609,7 @@ impl<'a, 'b> Rem<&'b BigDecimal> for &'a BigDecimal { #[inline] fn rem(self, other: &BigDecimal) -> BigDecimal { - let scale = std::cmp::max(self.scale, other.scale); + let scale = cmp::max(self.scale, other.scale); let num = &self.int_val; let den = &other.int_val; @@ -1953,11 +1976,11 @@ impl ToBigInt for BigDecimal { /// Tools to help serializing/deserializing `BigDecimal`s #[cfg(feature = "serde")] mod bigdecimal_serde { + use crate::{fmt, TryFrom, FromStr}; + use super::BigDecimal; use serde::{de, ser}; - use std::convert::TryFrom; - use std::fmt; - use std::str::FromStr; + #[allow(unused_imports)] use num_traits::FromPrimitive; @@ -2108,9 +2131,9 @@ mod bigdecimal_serde { 0.001, 12.34, 5.0 * 0.03125, - ::std::f64::consts::PI, - ::std::f64::consts::PI * 10000.0, - ::std::f64::consts::PI * 30000.0, + crate::f64::consts::PI, + crate::f64::consts::PI * 10000.0, + crate::f64::consts::PI * 30000.0, ]; for n in vals { let expected = BigDecimal::from_f64(n).unwrap(); @@ -2123,12 +2146,13 @@ mod bigdecimal_serde { #[rustfmt::skip] #[cfg(test)] mod bigdecimal_tests { - use BigDecimal; + use crate::{BigDecimal, FromStr, TryFrom}; use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One}; - use std::convert::TryFrom; - use std::str::FromStr; use num_bigint; + #[cfg(all(not(feature = "std"), feature = "alloc"))] + use crate::{vec, format, ToString}; + #[test] fn test_sum() { let vals = vec![ @@ -2232,8 +2256,8 @@ mod bigdecimal_tests { ("12", 12), ("-13", -13), ("111", 111), - ("-128", ::std::i8::MIN), - ("127", ::std::i8::MAX), + ("-128", i8::MIN), + ("127", i8::MAX), ]; for (s, n) in vals { let expected = BigDecimal::from_str(s).unwrap(); @@ -2292,8 +2316,8 @@ mod bigdecimal_tests { #[test] fn test_nan_float() { - assert!(BigDecimal::try_from(std::f32::NAN).is_err()); - assert!(BigDecimal::try_from(std::f64::NAN).is_err()); + assert!(BigDecimal::try_from(f32::NAN).is_err()); + assert!(BigDecimal::try_from(f64::NAN).is_err()); } #[test] @@ -2559,8 +2583,7 @@ mod bigdecimal_tests { #[test] fn test_hash_equal() { - use std::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; + use crate::{Hash, Hasher, DefaultHasher}; fn hash(obj: &T) -> u64 where T: Hash @@ -2596,8 +2619,7 @@ mod bigdecimal_tests { #[test] fn test_hash_not_equal() { - use std::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; + use crate::{Hash, Hasher, DefaultHasher}; fn hash(obj: &T) -> u64 where T: Hash @@ -2623,8 +2645,7 @@ mod bigdecimal_tests { #[test] fn test_hash_equal_scale() { - use std::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; + use crate::{Hash, Hasher, DefaultHasher}; fn hash(obj: &T) -> u64 where T: Hash diff --git a/src/with_alloc.rs b/src/with_alloc.rs new file mode 100644 index 0000000..511d71c --- /dev/null +++ b/src/with_alloc.rs @@ -0,0 +1,3 @@ +extern crate alloc; + +use alloc::{format, string, vec}; diff --git a/src/with_std.rs b/src/with_std.rs new file mode 100644 index 0000000..443c644 --- /dev/null +++ b/src/with_std.rs @@ -0,0 +1,4 @@ +use std::{cmp, convert, default, fmt, hash, num, ops, iter, str, string, i8, f32, f64}; + +#[cfg(test)] +use std::collections::hash_map::DefaultHasher; diff --git a/src/without_std.rs b/src/without_std.rs new file mode 100644 index 0000000..c12dc9f --- /dev/null +++ b/src/without_std.rs @@ -0,0 +1,11 @@ +use core::{cmp, convert, default, fmt, hash, num, ops, iter, str, i8, f32, f64}; + +#[cfg(test)] +extern crate siphasher; + +#[cfg(test)] +use siphasher::sip::SipHasher as DefaultHasher; + +// Without this import we get the following error: +// error[E0599]: no method named `powi` found for type `f64` in the current scope +use num_traits::float::FloatCore; From 4ba6fb1aaa1445689101818b2bfdb6d900e08672 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 23:39:58 -0400 Subject: [PATCH 041/100] Remove serde/std from std feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d0392e8..18cb408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,4 @@ siphasher = { version = "0.3.10", default-features = false } default = ["std"] alloc = [] string-only = [] -std = ["num-bigint/std", "num-integer/std", "num-traits/std", "serde/std"] +std = ["num-bigint/std", "num-integer/std", "num-traits/std"] From 2ee012106c623746510e979263f503626e370346 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 23:45:10 -0400 Subject: [PATCH 042/100] Add bignum to cargo keywords --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 18cb408..6f93866 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/akubera/bigdecimal-rs" keywords = [ "mathematics", "numerics", + "bignum", "decimal", "arbitrary-precision", "floating-point", From 4de5d3e9dadeb28b0527b65a578c5e032e77227e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 02:12:06 -0400 Subject: [PATCH 043/100] Run rustfmt on src/lib.rs --- src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cb6e198..8863e54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1324,7 +1324,6 @@ impl Mul for BigInt { } } - impl<'a> Mul for &'a BigInt { type Output = BigDecimal; @@ -1344,7 +1343,6 @@ impl<'a> Mul for &'a BigInt { } } - impl<'a, 'b> Mul<&'a BigDecimal> for &'b BigInt { type Output = BigDecimal; @@ -1377,7 +1375,6 @@ impl<'a> Mul<&'a BigDecimal> for BigInt { } } - forward_val_assignop!(impl MulAssign for BigDecimal, mul_assign); impl<'a> MulAssign<&'a BigDecimal> for BigDecimal { @@ -1408,8 +1405,6 @@ impl MulAssign for BigDecimal { } } - - impl_div_for_primitives!(); #[inline(always)] From cdf8de19ce9316fe4f1741893e7c7174c3880175 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 17:40:16 -0400 Subject: [PATCH 044/100] Add build-and-test:alloc job and set default rust-version for circleci --- .circleci/config.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2611eb2..0e4c0fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,7 @@ jobs: parameters: rust-version: type: string + default: "1.69.0" debian-version: type: string default: "bullseye" @@ -95,24 +96,22 @@ workflows: - build-and-test: name: build-and-test:latest - rust-version: "1.69.0" - build-and-test: matrix: parameters: - rust-version: - - "1.69.0" rust-features: - "--features='serde'" - "--features='serde,string-only'" - build-and-test: name: build-and-test:no-default-features - rust-version: "1.69.0" rust-features: "--no-default-features" + - build-and-test: + name: build-and-test:alloc + rust-features: "--no-default-features --features=alloc" + #- upload-coverage: - # rust-version: "1.69.0" # requires: # - build-and-test:latest - From 726800bdb8287a033002343bb76d53792fc7ef3f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 18:03:52 -0400 Subject: [PATCH 045/100] Set and use default rust-version in circleci jobs --- .circleci/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e4c0fb..5e15bdc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,10 +108,6 @@ workflows: name: build-and-test:no-default-features rust-features: "--no-default-features" - - build-and-test: - name: build-and-test:alloc - rust-features: "--no-default-features --features=alloc" - #- upload-coverage: # requires: # - build-and-test:latest From 3882fe23afedc7c97a0c10f8bb763ab555d8a269 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 18:04:32 -0400 Subject: [PATCH 046/100] Add no_std and alloc features ci jobs --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e15bdc..0e4c0fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,6 +108,10 @@ workflows: name: build-and-test:no-default-features rust-features: "--no-default-features" + - build-and-test: + name: build-and-test:alloc + rust-features: "--no-default-features --features=alloc" + #- upload-coverage: # requires: # - build-and-test:latest From 805d73ccccd1ed1f0b61fdb169dac556d5ea20db Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 21:49:14 -0400 Subject: [PATCH 047/100] Verticalize long import lists --- src/with_std.rs | 17 ++++++++++++++++- src/without_std.rs | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/with_std.rs b/src/with_std.rs index 443c644..2226e59 100644 --- a/src/with_std.rs +++ b/src/with_std.rs @@ -1,4 +1,19 @@ -use std::{cmp, convert, default, fmt, hash, num, ops, iter, str, string, i8, f32, f64}; +use std::{ + cmp, + convert, + default, + fmt, + hash, + num, + ops, + iter, + str, + string, + i8, + f32, + f64, +}; + #[cfg(test)] use std::collections::hash_map::DefaultHasher; diff --git a/src/without_std.rs b/src/without_std.rs index c12dc9f..0ed9ab8 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -1,4 +1,17 @@ -use core::{cmp, convert, default, fmt, hash, num, ops, iter, str, i8, f32, f64}; +use core::{ + cmp, + convert, + default, + fmt, + hash, + num, + ops, + iter, + str, + i8, + f32, + f64, +}; #[cfg(test)] extern crate siphasher; From 7e75dcdd0e99e3f93ba490329dcbad03a6e00ee1 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 9 May 2023 21:50:31 -0400 Subject: [PATCH 048/100] Replace std::mem::swap with crate::mem::swap --- src/lib.rs | 2 +- src/with_std.rs | 1 + src/without_std.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8863e54..28c9d5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1316,7 +1316,7 @@ impl Mul for BigInt { fn mul(mut self, mut rhs: BigDecimal) -> BigDecimal { if rhs.is_one() { rhs.scale = 0; - std::mem::swap(&mut rhs.int_val, &mut self); + crate::mem::swap(&mut rhs.int_val, &mut self); } else if !self.is_one() { rhs.int_val *= self; } diff --git a/src/with_std.rs b/src/with_std.rs index 2226e59..f5d9b4b 100644 --- a/src/with_std.rs +++ b/src/with_std.rs @@ -4,6 +4,7 @@ use std::{ default, fmt, hash, + mem, num, ops, iter, diff --git a/src/without_std.rs b/src/without_std.rs index 0ed9ab8..c586178 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -4,6 +4,7 @@ use core::{ default, fmt, hash, + mem, num, ops, iter, From 2ca8605b3c13b998c75cc68df98eabeac5131aa2 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 12 May 2023 18:09:45 -0400 Subject: [PATCH 049/100] Move with_alloc.rs behavior into without_std.rs --- src/lib.rs | 3 --- src/with_alloc.rs | 3 --- src/without_std.rs | 7 +++++++ 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 src/with_alloc.rs diff --git a/src/lib.rs b/src/lib.rs index 28c9d5c..349bbf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,9 +60,6 @@ include!("./with_std.rs"); #[cfg(not(feature = "std"))] include!("./without_std.rs"); -#[cfg(all(not(feature = "std"), feature = "alloc"))] -include!("./with_alloc.rs"); - use crate::cmp::Ordering; use crate::convert::TryFrom; use crate::default::Default; diff --git a/src/with_alloc.rs b/src/with_alloc.rs deleted file mode 100644 index 511d71c..0000000 --- a/src/with_alloc.rs +++ /dev/null @@ -1,3 +0,0 @@ -extern crate alloc; - -use alloc::{format, string, vec}; diff --git a/src/without_std.rs b/src/without_std.rs index c586178..de39bf2 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -23,3 +23,10 @@ use siphasher::sip::SipHasher as DefaultHasher; // Without this import we get the following error: // error[E0599]: no method named `powi` found for type `f64` in the current scope use num_traits::float::FloatCore; + + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::{format, string, vec}; From 4a8e9e09417ae33c200cca852a1f8f94ab2b3ea5 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 21 May 2023 00:35:08 -0400 Subject: [PATCH 050/100] Replace std usages with crate --- src/lib.rs | 17 +++++++---------- src/parsing.rs | 23 +++++++++-------------- src/parsing.tests.parse_from_f32.rs | 10 +++++----- src/parsing.tests.parse_from_f64.rs | 10 +++++----- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 349bbf5..a7e0265 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2138,13 +2138,10 @@ mod bigdecimal_serde { #[rustfmt::skip] #[cfg(test)] mod bigdecimal_tests { - use crate::{BigDecimal, FromStr, TryFrom}; + use crate::{BigDecimal, ToString, FromStr, TryFrom}; use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One}; use num_bigint; - #[cfg(all(not(feature = "std"), feature = "alloc"))] - use crate::{vec, format, ToString}; - #[test] fn test_sum() { let vals = vec![ @@ -2270,9 +2267,9 @@ mod bigdecimal_tests { ("0.001000000047497451305389404296875", 0.001), ("12.340000152587890625", 12.34), ("0.15625", 0.15625), - ("3.1415927410125732421875", ::std::f32::consts::PI), - ("31415.927734375", ::std::f32::consts::PI * 10000.0), - ("94247.78125", ::std::f32::consts::PI * 30000.0), + ("3.1415927410125732421875", crate::f32::consts::PI), + ("31415.927734375", crate::f32::consts::PI * 10000.0), + ("94247.78125", crate::f32::consts::PI * 30000.0), ("1048576", 1048576.), ]; for (s, n) in vals { @@ -2294,9 +2291,9 @@ mod bigdecimal_tests { ("12.339999999999999857891452847979962825775146484375", 12.34), ("0.15625", 5.0 * 0.03125), ("0.333333333333333314829616256247390992939472198486328125", 1.0 / 3.0), - ("3.141592653589793115997963468544185161590576171875", ::std::f64::consts::PI), - ("31415.926535897931898944079875946044921875", ::std::f64::consts::PI * 10000.0f64), - ("94247.779607693795696832239627838134765625", ::std::f64::consts::PI * 30000.0f64), + ("3.141592653589793115997963468544185161590576171875", crate::f64::consts::PI), + ("31415.926535897931898944079875946044921875", crate::f64::consts::PI * 10000.0f64), + ("94247.779607693795696832239627838134765625", crate::f64::consts::PI * 30000.0f64), ]; for (s, n) in vals { let expected = BigDecimal::from_str(s).unwrap(); diff --git a/src/parsing.rs b/src/parsing.rs index 7337e30..de314eb 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -3,6 +3,7 @@ use super::{BigDecimal, ParseBigDecimalError}; use num_bigint::{BigInt, BigUint, Sign}; +use num_traits::Zero; /// Try creating bigdecimal from f32 @@ -10,7 +11,7 @@ use num_bigint::{BigInt, BigUint, Sign}; /// Non "normal" values will return Error case /// pub(crate) fn try_parse_from_f32(n: f32) -> Result { - use std::num::FpCategory::*; + use crate::num::FpCategory::*; match n.classify() { Nan => Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), @@ -49,15 +50,12 @@ fn split_f32_into_parts(f: f32) -> (u32, i64, Sign) { /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { - use std::cmp::Ordering::*; + use crate::cmp::Ordering::*; let bits = n.to_bits(); if (bits << 1) == 0 { - return BigDecimal { - int_val: BigInt::new(Sign::NoSign, vec![0]), - scale: 0, - }; + return Zero::zero(); } // n = frac * 2^pow @@ -71,7 +69,7 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { scale = 0; } Less => { - let trailing_zeros = std::cmp::min(frac.trailing_zeros(), -pow as u32); + let trailing_zeros = crate::cmp::min(frac.trailing_zeros(), -pow as u32); let reduced_frac = frac >> trailing_zeros; let reduced_pow = pow + trailing_zeros as i64; @@ -110,7 +108,7 @@ mod test_parse_from_f32 { /// Non "normal" values will return Error case /// pub(crate) fn try_parse_from_f64(n: f64) -> Result { - use std::num::FpCategory::*; + use crate::num::FpCategory::*; match n.classify() { Nan => Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), @@ -149,16 +147,13 @@ fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) { /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { - use std::cmp::Ordering::*; + use crate::cmp::Ordering::*; let bits = n.to_bits(); // shift right by 1 bit to handle -0.0 if (bits << 1) == 0 { - return BigDecimal { - int_val: BigInt::new(Sign::NoSign, vec![0]), - scale: 0, - }; + return Zero::zero(); } // n = frac * 2^pow @@ -173,7 +168,7 @@ pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { scale = 0; } Less => { - let trailing_zeros = std::cmp::min(frac.trailing_zeros(), -pow as u32); + let trailing_zeros = crate::cmp::min(frac.trailing_zeros(), -pow as u32); let reduced_frac = frac >> trailing_zeros; let reduced_pow = pow + trailing_zeros as i64; diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index fb9474c..32c7b7c 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -2,7 +2,7 @@ use paste::paste; -use std::f32; +use crate::f32; macro_rules! impl_test { ($name:ident : $input:literal == $expected:literal) => { @@ -60,28 +60,28 @@ impl_test!(_158456325029e18 : 1.58456325029e+29 == "1584563250285286751870879006 #[test] fn case_f32_min() { - let n = std::f32::MIN; + let n = f32::MIN; let d = parse_from_f32(n); assert_eq!(d, "-340282346638528859811704183484516925440".parse().unwrap()); } #[test] fn case_f32_max() { - let n = std::f32::MAX; + let n = f32::MAX; let d = parse_from_f32(n); assert_eq!(d, "340282346638528859811704183484516925440".parse().unwrap()); } #[test] fn case_f32_epsilon() { - let n = std::f32::EPSILON; + let n = f32::EPSILON; let d = parse_from_f32(n); assert_eq!(d, "1.1920928955078125E-7".parse().unwrap()); } #[test] fn case_f32_pi() { - let n = std::f32::consts::PI; + let n = f32::consts::PI; let d = parse_from_f32(n); assert_eq!(d, "3.1415927410125732421875".parse().unwrap()); } diff --git a/src/parsing.tests.parse_from_f64.rs b/src/parsing.tests.parse_from_f64.rs index 3e88de3..4b81dc6 100644 --- a/src/parsing.tests.parse_from_f64.rs +++ b/src/parsing.tests.parse_from_f64.rs @@ -2,7 +2,7 @@ use paste::paste; -use std::f64; +use crate::f64; macro_rules! impl_test { ($input:literal == $expected:literal) => { @@ -53,28 +53,28 @@ impl_test!(_3105036184601418e246 : bits:0b_0_11100000000_00000000000000000000000 #[test] fn case_f64_min() { - let n = std::f64::MIN; + let n = f64::MIN; let d = parse_from_f64(n); assert_eq!(d, "-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368".parse().unwrap()); } #[test] fn case_f64_max() { - let n = std::f64::MAX; + let n = f64::MAX; let d = parse_from_f64(n); assert_eq!(d, "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368".parse().unwrap()); } #[test] fn case_f64_epsilon() { - let n = std::f64::EPSILON; + let n = f64::EPSILON; let d = parse_from_f64(n); assert_eq!(d, "2.220446049250313080847263336181640625e-16".parse().unwrap()); } #[test] fn case_f64_pi() { - let n = std::f64::consts::PI; + let n = f64::consts::PI; let d = parse_from_f64(n); assert_eq!(d, "3.141592653589793115997963468544185161590576171875".parse().unwrap()); } From 3d22823a66d4b079d97036d43e92e4ea6592ff2e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 21 May 2023 00:52:49 -0400 Subject: [PATCH 051/100] Remove alloc feature --- Cargo.toml | 1 - src/without_std.rs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f93866..9925b59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,5 @@ siphasher = { version = "0.3.10", default-features = false } [features] default = ["std"] -alloc = [] string-only = [] std = ["num-bigint/std", "num-integer/std", "num-traits/std"] diff --git a/src/without_std.rs b/src/without_std.rs index de39bf2..c42a921 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -24,9 +24,7 @@ use siphasher::sip::SipHasher as DefaultHasher; // error[E0599]: no method named `powi` found for type `f64` in the current scope use num_traits::float::FloatCore; - -#[cfg(feature = "alloc")] +#[macro_use] extern crate alloc; -#[cfg(feature = "alloc")] use alloc::{format, string, vec}; From b9caa76963032ed540cd70cd159e1ddb259d48c5 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 21 May 2023 13:01:56 -0400 Subject: [PATCH 052/100] Remove alloc-feature circleci test job --- .circleci/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e4c0fb..5e15bdc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,10 +108,6 @@ workflows: name: build-and-test:no-default-features rust-features: "--no-default-features" - - build-and-test: - name: build-and-test:alloc - rust-features: "--no-default-features --features=alloc" - #- upload-coverage: # requires: # - build-and-test:latest From 822d91b85e1617d572f90024e82b43a4b2729b7b Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 21 May 2023 16:12:05 -0400 Subject: [PATCH 053/100] Fix warnings in without_std.rs --- src/without_std.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/without_std.rs b/src/without_std.rs index c42a921..b05b5d3 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -22,9 +22,11 @@ use siphasher::sip::SipHasher as DefaultHasher; // Without this import we get the following error: // error[E0599]: no method named `powi` found for type `f64` in the current scope +#[allow(unused_imports)] use num_traits::float::FloatCore; +#[allow(unused_imports)] #[macro_use] extern crate alloc; -use alloc::{format, string, vec}; +use alloc::string; From dcb06d5e9e01d04508cb22c61fb84373bb07f140 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 21 May 2023 16:12:59 -0400 Subject: [PATCH 054/100] Ignore rust/format check in circleci --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e15bdc..cc9a4ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,8 +72,9 @@ jobs: - image: cimg/rust:1.69 steps: - checkout - - rust/build - - rust/format + - rust/build: + with_cache: false + # - rust/format # - rust/clippy - rust/test - run: From 82ddc5e70076a7d188009b186dfb4c99dfc4855a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 19 Mar 2023 20:05:39 -0400 Subject: [PATCH 055/100] Add test for rounding large numbers --- src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a7e0265..0fe0380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2828,6 +2828,17 @@ mod bigdecimal_tests { } } + #[test] + fn round_large_number() { + use super::BigDecimal; + + let z = BigDecimal::from_str("3.4613133327063255443352353815722045816611958409944513040035462804475524").unwrap(); + let expected = BigDecimal::from_str("11.98068998717057027117831038176842424089721245689422762852009735276472").unwrap(); + let zsq = &z*&z; + let zsq = zsq.round(70); + debug_assert_eq!(zsq, expected); + } + #[test] fn test_is_integer() { let true_vals = vec![ From ded4e6427b894ee4c0bebfa27b64cb103f5af7f9 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 19 Mar 2023 21:20:59 -0400 Subject: [PATCH 056/100] Reimplement round using to_radix_le(100) --- src/lib.rs | 62 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0fe0380..1338fc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -623,22 +623,56 @@ impl BigDecimal { return self.clone(); } - let mut number = bigint.clone(); - if number < BigInt::zero() { - number = -number; - } - for _ in 0..(need_to_round_digits - 1) { - number /= 10; + let (sign, uint) = bigint.into_parts(); + let double_digits = uint.to_radix_le(100); + + let double_digits_to_remove = decimal_part_digits - round_digits; + + if double_digits_to_remove <= 0 { + return self.clone(); } - let digit = number % 10; - if digit <= BigInt::from(4) { - self.with_scale(round_digits) - } else if bigint.is_negative() { - self.with_scale(round_digits) - BigDecimal::new(BigInt::from(1), round_digits) + let (rounding_idx, rounding_offset) = num_integer::div_rem(double_digits_to_remove as usize, 2); + let (low_digits, high_digits) = double_digits.as_slice().split_at(rounding_idx); + + let mut unrounded_uint = num_bigint::BigUint::from_radix_le(high_digits, 100).unwrap(); + + let rounded_uint; + if rounding_offset == 0 { + let high_digit = high_digits[0] % 10; + let (&top, rest) = low_digits.split_last().unwrap_or((&0u8, &[])); + let (low_digit, lower_digit) = num_integer::div_rem(top, 10); + let trailing_zeros = lower_digit == 0 && rest.iter().all(|&d| d == 0); + + let rounding = if low_digit < 5 { + 0 + } else if low_digit > 5 || !trailing_zeros { + 1 + } else { + high_digit % 2 + }; + + rounded_uint = unrounded_uint + rounding; } else { - self.with_scale(round_digits) + BigDecimal::new(BigInt::from(1), round_digits) + let (high_digit, low_digit) = num_integer::div_rem(high_digits[0], 10); + + let trailing_zeros = low_digits.iter().all(|&d| d == 0); + + let rounding = if low_digit < 5 { + 0 + } else if low_digit > 5 || !trailing_zeros { + 1 + } else { + high_digit % 2 + }; + + // shift unrounded_uint down, + unrounded_uint /= num_bigint::BigUint::from_u8(10).unwrap(); + rounded_uint = unrounded_uint + rounding; } + + let rounded_int = num_bigint::BigInt::from_biguint(sign, rounded_uint); + BigDecimal::new(rounded_int, round_digits) } /// Return true if this number has zero fractional part (is equal @@ -2797,7 +2831,7 @@ mod bigdecimal_tests { #[test] fn test_round() { let test_cases = vec![ - ("1.45", 1, "1.5"), + ("1.45", 1, "1.4"), ("1.444445", 1, "1.4"), ("1.44", 1, "1.4"), ("0.444", 2, "0.44"), @@ -2833,7 +2867,7 @@ mod bigdecimal_tests { use super::BigDecimal; let z = BigDecimal::from_str("3.4613133327063255443352353815722045816611958409944513040035462804475524").unwrap(); - let expected = BigDecimal::from_str("11.98068998717057027117831038176842424089721245689422762852009735276472").unwrap(); + let expected = BigDecimal::from_str("11.9806899871705702711783103817684242408972124568942276285200973527647213").unwrap(); let zsq = &z*&z; let zsq = zsq.round(70); debug_assert_eq!(zsq, expected); From 99bf9b3401e29e4dab8e12853dda1e84da8c5864 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 19 Mar 2023 23:24:38 -0400 Subject: [PATCH 057/100] Handle case where highest digit is left of rounding point --- src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1338fc7..3400ff4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -617,23 +617,55 @@ impl BigDecimal { /// Return number rounded to round_digits precision after the decimal point pub fn round(&self, round_digits: i64) -> BigDecimal { - let (bigint, decimal_part_digits) = self.as_bigint_and_exponent(); - let need_to_round_digits = decimal_part_digits - round_digits; - if round_digits >= 0 && need_to_round_digits <= 0 { - return self.clone(); + // we have fewer digits than we need, no rounding + if round_digits >= self.scale { + return self.with_scale(round_digits); } - let (sign, uint) = bigint.into_parts(); - let double_digits = uint.to_radix_le(100); + let (sign, double_digits) = self.int_val.to_radix_le(100); - let double_digits_to_remove = decimal_part_digits - round_digits; + let last_is_double_digit = *double_digits.last().unwrap() >= 10; + let digit_count = (double_digits.len() - 1) * 2 + 1 + last_is_double_digit as usize; - if double_digits_to_remove <= 0 { - return self.clone(); + // relevant digit positions: each "pos" is position of 10^{pos} + let least_significant_pos = -self.scale; + let most_sig_digit_pos = digit_count as i64 + least_significant_pos - 1; + let rounding_pos = -round_digits; + + // digits are too small, round to zero + if rounding_pos > most_sig_digit_pos + 1 { + return BigDecimal::zero(); + } + + // highest digit is next to rounding point + if rounding_pos == most_sig_digit_pos + 1 { + let (&last_double_digit, remaining) = double_digits.split_last().unwrap(); + + let mut trailing_zeros = remaining.iter().all(|&d| d == 0); + + let last_digit = if last_is_double_digit { + let (high, low) = num_integer::div_rem(last_double_digit, 10); + trailing_zeros &= low == 0; + high + } else { + last_double_digit + }; + + if last_digit > 5 || (last_digit == 5 && !trailing_zeros) { + return BigDecimal::new(BigInt::one(), round_digits); + } + + return BigDecimal::zero(); } + let double_digits_to_remove = self.scale - round_digits; + debug_assert!(double_digits_to_remove > 0); + let (rounding_idx, rounding_offset) = num_integer::div_rem(double_digits_to_remove as usize, 2); + debug_assert!(rounding_idx <= double_digits.len()); + let (low_digits, high_digits) = double_digits.as_slice().split_at(rounding_idx); + debug_assert!(high_digits.len() > 0); let mut unrounded_uint = num_bigint::BigUint::from_radix_le(high_digits, 100).unwrap(); From cfcf7fea75b1b8878c0c14e766436824a1e2cd9f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 19 Mar 2023 23:31:10 -0400 Subject: [PATCH 058/100] Add more rounding test cases --- src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3400ff4..42e7731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2867,15 +2867,28 @@ mod bigdecimal_tests { ("1.444445", 1, "1.4"), ("1.44", 1, "1.4"), ("0.444", 2, "0.44"), + ("4.5", 0, "4"), + ("4.05", 1, "4.0"), + ("4.050", 1, "4.0"), + ("4.15", 1, "4.2"), ("0.0045", 2, "0.00"), + ("5.5", -1, "10"), ("-1.555", 2, "-1.56"), ("-1.555", 99, "-1.555"), ("5.5", 0, "6"), ("-1", -1, "0"), - ("5", -1, "10"), + ("5", -1, "0"), ("44", -1, "40"), ("44", -99, "0"), + ("44", 99, "44"), + ("1.4499999999", -1, "0"), + ("1.4499999999", 0, "1"), ("1.4499999999", 1, "1.4"), + ("1.4499999999", 2, "1.45"), + ("1.4499999999", 3, "1.450"), + ("1.4499999999", 4, "1.4500"), + ("1.4499999999", 10, "1.4499999999"), + ("1.4499999999", 15, "1.449999999900000"), ("-1.4499999999", 1, "-1.4"), ("1.449999999", 1, "1.4"), ("-9999.444455556666", 10, "-9999.4444555567"), From b35a851246465f724fde83397ab257bbef48dd32 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 03:19:52 -0400 Subject: [PATCH 059/100] Add build.rs which generates the default_precision.rs file --- build.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 build.rs diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..a299729 --- /dev/null +++ b/build.rs @@ -0,0 +1,35 @@ + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() -> std::io::Result<()> { + let outdir = match std::env::var_os("OUT_DIR") { + None => return Ok(()), + Some(outdir) => outdir, + }; + let outdir_path = PathBuf::from(outdir); + + write_default_precision(&outdir_path, "default_precision.rs")?; + Ok(()) +} + +/// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION +fn write_default_precision(outdir_path: &PathBuf, filename: &str) -> std::io::Result<()> +{ + + let default_prec = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION") + .map(|s| s.parse::().expect("$RUST_BIGDECIMAL_DEFAULT_PRECISION must be an integer > 0")) + .map(|nz_num| nz_num.into()) + .unwrap_or(100u32); + + let default_precision_rs_path = outdir_path.join(filename); + + let mut default_precision_rs = File::create(&default_precision_rs_path).expect("Could not create default_precision.rs"); + write!(default_precision_rs, "const DEFAULT_PRECISION: u64 = {};", default_prec)?; + + println!("cargo:rerun-if-changed={}", default_precision_rs_path.display()); + + Ok(()) +} From c67009ad05c2611a3994b17708106cdd49be92ea Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 03:21:44 -0400 Subject: [PATCH 060/100] Include default_precision.rs in lib.rs --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 42e7731..25a2bb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,10 @@ pub use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; #[allow(clippy::approx_constant)] // requires Rust 1.43.0 const LOG2_10: f64 = 3.321928094887362_f64; + +// const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100; +include!(concat!(env!("OUT_DIR"), "/default_precision.rs")); + #[macro_use] mod macros; From dcc3174c789d4bc19463d0ad478701ac7f41b11b Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 03:29:55 -0400 Subject: [PATCH 061/100] Use DEFAULT_PRECISION in BigDecimal::exp --- src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 25a2bb6..52aa804 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -731,6 +731,8 @@ impl BigDecimal { return BigDecimal::one(); } + let target_precision = DEFAULT_PRECISION; + let precision = self.digits(); let mut term = self.clone(); @@ -744,13 +746,13 @@ impl BigDecimal { // ∑ term=x^n/n! result += impl_division(term.int_val.clone(), &factorial, term.scale, 117 + precision); - let trimmed_result = result.with_prec(105); + let trimmed_result = result.with_prec(target_precision + 5); if prev_result == trimmed_result { - return trimmed_result.with_prec(100); + return trimmed_result.with_prec(target_precision); } prev_result = trimmed_result; } - return result.with_prec(100); + unreachable!("Loop did not converge") } #[must_use] From 7c48a7c105a9f1992105ebbbf6547384f7c8f155 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 03:31:17 -0400 Subject: [PATCH 062/100] Use DEFAULT_PRECISION in BigDecimal::{sqrt,cbrt,inverse} --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52aa804..34492ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -450,7 +450,7 @@ impl BigDecimal { // } // TODO: Use context variable to set precision - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; let next_iteration = move |r: BigDecimal| { // division needs to be precise to (at least) one extra digit @@ -518,7 +518,7 @@ impl BigDecimal { }; // TODO: Use context variable to set precision - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; let three = BigDecimal::from(3); @@ -585,7 +585,7 @@ impl BigDecimal { } }; - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; let next_iteration = move |r: BigDecimal| { let two = BigDecimal::from(2); let tmp = two - self * &r; From b2a4d9d0c6765b0f3ddd6b104002aa91181e013d Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 04:01:40 -0400 Subject: [PATCH 063/100] Update README to include notes on default precision --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 5bad499..0dc3274 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,21 @@ sqrt(2) = 1.41421356237309504880168872420969807856967187537694807317667973799073 ``` +#### Default precision + +Default precision may be set at compile time with the environment variable `RUST_BIGDECIMAL_DEFAULT_PRECISION`. +The default value of this variable is 100. + +This will be used as maximum precision for operations which may produce infinite digits (inverse, sqrt, ...). + +Note that other operations, such as multiplication, will preserve all digits, so multiplying two 70 digit numbers +will result in one 140 digit number. +The user will have to manually trim the number of digits after calculations to reasonable amounts using the +`x.with_prec(30)` method. + +A new set of methods with explicit precision and rounding modes is being worked on, but even after those +are introduced the default precision will have to be used as the implicit value. + ## Improvements Work is being done on this codebase again and there are many features From 91c78d3f114bdc5e648466dfaafcf4de16aad817 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 14:09:52 -0400 Subject: [PATCH 064/100] Wrap std:: or core:: modules in namespace module 'stdlib' --- src/lib.rs | 59 ++++++++++++++++------------- src/parsing.rs | 26 ++++++------- src/parsing.tests.parse_from_f32.rs | 2 +- src/parsing.tests.parse_from_f64.rs | 2 +- src/with_std.rs | 42 +++++++++++--------- src/without_std.rs | 49 +++++++++++++----------- 6 files changed, 97 insertions(+), 83 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 34492ca..5ac9912 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,15 +60,17 @@ include!("./with_std.rs"); #[cfg(not(feature = "std"))] include!("./without_std.rs"); -use crate::cmp::Ordering; -use crate::convert::TryFrom; -use crate::default::Default; -use crate::hash::{Hash, Hasher}; -use crate::num::{ParseFloatError, ParseIntError}; -use crate::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; -use crate::iter::Sum; -use crate::str::FromStr; -use crate::string::{String, ToString}; +// make available some standard items +use self::stdlib::cmp::{self, Ordering}; +use self::stdlib::convert::TryFrom; +use self::stdlib::default::Default; +use self::stdlib::hash::{Hash, Hasher}; +use self::stdlib::num::{ParseFloatError, ParseIntError}; +use self::stdlib::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use self::stdlib::iter::Sum; +use self::stdlib::str::FromStr; +use self::stdlib::string::{String, ToString}; +use self::stdlib::fmt; use num_bigint::{BigInt, ParseBigIntError, Sign, ToBigInt}; use num_integer::Integer as IntegerTrait; @@ -206,9 +208,9 @@ impl BigDecimal { /// ``` #[inline] pub fn parse_bytes(buf: &[u8], radix: u32) -> Option { - str::from_utf8(buf) - .ok() - .and_then(|s| BigDecimal::from_str_radix(s, radix).ok()) + stdlib::str::from_utf8(buf) + .ok() + .and_then(|s| BigDecimal::from_str_radix(s, radix).ok()) } /// Return a new BigDecimal object equivalent to self, with internal @@ -1385,7 +1387,7 @@ impl Mul for BigInt { fn mul(mut self, mut rhs: BigDecimal) -> BigDecimal { if rhs.is_one() { rhs.scale = 0; - crate::mem::swap(&mut rhs.int_val, &mut self); + stdlib::mem::swap(&mut rhs.int_val, &mut self); } else if !self.is_one() { rhs.int_val *= self; } @@ -2040,7 +2042,7 @@ impl ToBigInt for BigDecimal { /// Tools to help serializing/deserializing `BigDecimal`s #[cfg(feature = "serde")] mod bigdecimal_serde { - use crate::{fmt, TryFrom, FromStr}; + use stdlib::{fmt, TryFrom, FromStr}; use super::BigDecimal; use serde::{de, ser}; @@ -2195,9 +2197,9 @@ mod bigdecimal_serde { 0.001, 12.34, 5.0 * 0.03125, - crate::f64::consts::PI, - crate::f64::consts::PI * 10000.0, - crate::f64::consts::PI * 30000.0, + stdlib::f64::consts::PI, + stdlib::f64::consts::PI * 10000.0, + stdlib::f64::consts::PI * 30000.0, ]; for n in vals { let expected = BigDecimal::from_f64(n).unwrap(); @@ -2210,7 +2212,7 @@ mod bigdecimal_serde { #[rustfmt::skip] #[cfg(test)] mod bigdecimal_tests { - use crate::{BigDecimal, ToString, FromStr, TryFrom}; + use crate::{stdlib, BigDecimal, ToString, FromStr, TryFrom}; use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One}; use num_bigint; @@ -2339,9 +2341,9 @@ mod bigdecimal_tests { ("0.001000000047497451305389404296875", 0.001), ("12.340000152587890625", 12.34), ("0.15625", 0.15625), - ("3.1415927410125732421875", crate::f32::consts::PI), - ("31415.927734375", crate::f32::consts::PI * 10000.0), - ("94247.78125", crate::f32::consts::PI * 30000.0), + ("3.1415927410125732421875", stdlib::f32::consts::PI), + ("31415.927734375", stdlib::f32::consts::PI * 10000.0), + ("94247.78125", stdlib::f32::consts::PI * 30000.0), ("1048576", 1048576.), ]; for (s, n) in vals { @@ -2363,9 +2365,9 @@ mod bigdecimal_tests { ("12.339999999999999857891452847979962825775146484375", 12.34), ("0.15625", 5.0 * 0.03125), ("0.333333333333333314829616256247390992939472198486328125", 1.0 / 3.0), - ("3.141592653589793115997963468544185161590576171875", crate::f64::consts::PI), - ("31415.926535897931898944079875946044921875", crate::f64::consts::PI * 10000.0f64), - ("94247.779607693795696832239627838134765625", crate::f64::consts::PI * 30000.0f64), + ("3.141592653589793115997963468544185161590576171875", stdlib::f64::consts::PI), + ("31415.926535897931898944079875946044921875", stdlib::f64::consts::PI * 10000.0f64), + ("94247.779607693795696832239627838134765625", stdlib::f64::consts::PI * 30000.0f64), ]; for (s, n) in vals { let expected = BigDecimal::from_str(s).unwrap(); @@ -2644,7 +2646,8 @@ mod bigdecimal_tests { #[test] fn test_hash_equal() { - use crate::{Hash, Hasher, DefaultHasher}; + use stdlib::DefaultHasher; + use stdlib::hash::{Hash, Hasher}; fn hash(obj: &T) -> u64 where T: Hash @@ -2680,7 +2683,8 @@ mod bigdecimal_tests { #[test] fn test_hash_not_equal() { - use crate::{Hash, Hasher, DefaultHasher}; + use stdlib::DefaultHasher; + use stdlib::hash::{Hash, Hasher}; fn hash(obj: &T) -> u64 where T: Hash @@ -2706,7 +2710,8 @@ mod bigdecimal_tests { #[test] fn test_hash_equal_scale() { - use crate::{Hash, Hasher, DefaultHasher}; + use stdlib::DefaultHasher; + use stdlib::hash::{Hash, Hasher}; fn hash(obj: &T) -> u64 where T: Hash diff --git a/src/parsing.rs b/src/parsing.rs index de314eb..8bd917b 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -2,6 +2,8 @@ use super::{BigDecimal, ParseBigDecimalError}; +use stdlib::cmp::{self, Ordering}; + use num_bigint::{BigInt, BigUint, Sign}; use num_traits::Zero; @@ -11,7 +13,7 @@ use num_traits::Zero; /// Non "normal" values will return Error case /// pub(crate) fn try_parse_from_f32(n: f32) -> Result { - use crate::num::FpCategory::*; + use stdlib::num::FpCategory::*; match n.classify() { Nan => Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), @@ -50,8 +52,6 @@ fn split_f32_into_parts(f: f32) -> (u32, i64, Sign) { /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { - use crate::cmp::Ordering::*; - let bits = n.to_bits(); if (bits << 1) == 0 { @@ -64,12 +64,12 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { let result; let scale; match pow.cmp(&0) { - Equal => { + Ordering::Equal => { result = BigUint::from(frac); scale = 0; } - Less => { - let trailing_zeros = crate::cmp::min(frac.trailing_zeros(), -pow as u32); + Ordering::Less => { + let trailing_zeros = cmp::min(frac.trailing_zeros(), -pow as u32); let reduced_frac = frac >> trailing_zeros; let reduced_pow = pow + trailing_zeros as i64; @@ -80,7 +80,7 @@ pub(crate) fn parse_from_f32(n: f32) -> BigDecimal { result = reduced_frac * shift; scale = -reduced_pow; } - Greater => { + Ordering::Greater => { let shift = BigUint::from(2u8).pow(pow.abs() as u32); result = frac * shift; @@ -108,7 +108,7 @@ mod test_parse_from_f32 { /// Non "normal" values will return Error case /// pub(crate) fn try_parse_from_f64(n: f64) -> Result { - use crate::num::FpCategory::*; + use stdlib::num::FpCategory::*; match n.classify() { Nan => Err(ParseBigDecimalError::Other("NAN".into())), Infinite => Err(ParseBigDecimalError::Other("Infinite".into())), @@ -147,8 +147,6 @@ fn split_f64_into_parts(f: f64) -> (u64, i64, Sign) { /// Non "normal" values is undefined behavior /// pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { - use crate::cmp::Ordering::*; - let bits = n.to_bits(); // shift right by 1 bit to handle -0.0 @@ -163,12 +161,12 @@ pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { let result; let scale; match pow.cmp(&0) { - Equal => { + Ordering::Equal => { result = BigUint::from(frac); scale = 0; } - Less => { - let trailing_zeros = crate::cmp::min(frac.trailing_zeros(), -pow as u32); + Ordering::Less => { + let trailing_zeros = cmp::min(frac.trailing_zeros(), -pow as u32); let reduced_frac = frac >> trailing_zeros; let reduced_pow = pow + trailing_zeros as i64; @@ -179,7 +177,7 @@ pub(crate) fn parse_from_f64(n: f64) -> BigDecimal { result = reduced_frac * shift; scale = -reduced_pow; } - Greater => { + Ordering::Greater => { let shift = BigUint::from(2u8).pow(pow as u32); result = frac * shift; scale = 0; diff --git a/src/parsing.tests.parse_from_f32.rs b/src/parsing.tests.parse_from_f32.rs index 32c7b7c..ab9fd29 100644 --- a/src/parsing.tests.parse_from_f32.rs +++ b/src/parsing.tests.parse_from_f32.rs @@ -2,7 +2,7 @@ use paste::paste; -use crate::f32; +use stdlib::f32; macro_rules! impl_test { ($name:ident : $input:literal == $expected:literal) => { diff --git a/src/parsing.tests.parse_from_f64.rs b/src/parsing.tests.parse_from_f64.rs index 4b81dc6..5f8c195 100644 --- a/src/parsing.tests.parse_from_f64.rs +++ b/src/parsing.tests.parse_from_f64.rs @@ -2,7 +2,7 @@ use paste::paste; -use crate::f64; +use stdlib::f64; macro_rules! impl_test { ($input:literal == $expected:literal) => { diff --git a/src/with_std.rs b/src/with_std.rs index f5d9b4b..4cddf7c 100644 --- a/src/with_std.rs +++ b/src/with_std.rs @@ -1,20 +1,26 @@ -use std::{ - cmp, - convert, - default, - fmt, - hash, - mem, - num, - ops, - iter, - str, - string, - i8, - f32, - f64, -}; +// Wrap std:: modules in namespace +#[allow(unused_imports)] +mod stdlib { -#[cfg(test)] -use std::collections::hash_map::DefaultHasher; + pub use std::{ + cmp, + convert, + default, + fmt, + hash, + mem, + num, + ops, + iter, + str, + string, + i8, + f32, + f64, + }; + + + #[cfg(test)] + pub use std::collections::hash_map::DefaultHasher; +} diff --git a/src/without_std.rs b/src/without_std.rs index b05b5d3..8e35874 100644 --- a/src/without_std.rs +++ b/src/without_std.rs @@ -1,32 +1,37 @@ -use core::{ - cmp, - convert, - default, - fmt, - hash, - mem, - num, - ops, - iter, - str, - i8, - f32, - f64, -}; +#[allow(unused_imports)] +#[macro_use] +extern crate alloc; #[cfg(test)] extern crate siphasher; -#[cfg(test)] -use siphasher::sip::SipHasher as DefaultHasher; - // Without this import we get the following error: -// error[E0599]: no method named `powi` found for type `f64` in the current scope +// error[E0599]: no method naemed `powi` found for type `f64` in the current scope #[allow(unused_imports)] use num_traits::float::FloatCore; +// Wrap core:: modules in namespace #[allow(unused_imports)] -#[macro_use] -extern crate alloc; +mod stdlib { + + pub use core::{ + cmp, + convert, + default, + fmt, + hash, + mem, + num, + ops, + iter, + str, + i8, + f32, + f64, + }; + + #[cfg(test)] + pub use siphasher::sip::SipHasher as DefaultHasher; -use alloc::string; + pub use alloc::string; +} From bdbb94bd3a6160af75e6ff087a11f2323d55532e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 25 Aug 2017 20:41:36 -0400 Subject: [PATCH 065/100] Add rounding module with RoundingMode enum --- src/lib.rs | 1 + src/rounding.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/rounding.rs diff --git a/src/lib.rs b/src/lib.rs index 5ac9912..eaec869 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,7 @@ mod macros; extern crate paste; mod parsing; +pub mod rounding; #[inline(always)] fn ten_to_the(pow: u64) -> BigInt { diff --git a/src/rounding.rs b/src/rounding.rs new file mode 100644 index 0000000..30dc9ea --- /dev/null +++ b/src/rounding.rs @@ -0,0 +1,97 @@ +//! Rounding structures and subroutines + +use crate::Sign; + +/// Determines how to calculate the last digit of the number +/// +/// Default rounding mode is HalfUp +/// +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum RoundingMode { + /// Always round away from zero + /// + /// + /// * 5.5 → 6.0 + /// * 2.5 → 3.0 + /// * 1.6 → 2.0 + /// * 1.1 → 2.0 + /// * -1.1 → -2.0 + /// * -1.6 → -2.0 + /// * -2.5 → -3.0 + /// * -5.5 → -6.0 + Up, + + /// Always round towards zero + /// + /// * 5.5 → 5.0 + /// * 2.5 → 2.0 + /// * 1.6 → 1.0 + /// * 1.1 → 1.0 + /// * -1.1 → -1.0 + /// * -1.6 → -1.0 + /// * -2.5 → -2.0 + /// * -5.5 → -5.0 + Down, + + /// Towards +∞ + /// + /// * 5.5 → 6.0 + /// * 2.5 → 3.0 + /// * 1.6 → 2.0 + /// * 1.1 → 2.0 + /// * -1.1 → -1.0 + /// * -1.6 → -1.0 + /// * -2.5 → -2.0 + /// * -5.5 → -5.0 + Ceiling, + + /// Towards -∞ + /// + /// * 5.5 → 5.0 + /// * 2.5 → 2.0 + /// * 1.6 → 1.0 + /// * 1.1 → 1.0 + /// * -1.1 → -2.0 + /// * -1.6 → -2.0 + /// * -2.5 → -3.0 + /// * -5.5 → -6.0 + Floor, + + /// Round to 'nearest neighbor', or up if ending decimal is 5 + /// + /// * 5.5 → 6.0 + /// * 2.5 → 3.0 + /// * 1.6 → 2.0 + /// * 1.1 → 1.0 + /// * -1.1 → -1.0 + /// * -1.6 → -2.0 + /// * -2.5 → -3.0 + /// * -5.5 → -6.0 + HalfUp, + + /// Round to 'nearest neighbor', or down if ending decimal is 5 + /// + /// * 5.5 → 5.0 + /// * 2.5 → 2.0 + /// * 1.6 → 2.0 + /// * 1.1 → 1.0 + /// * -1.1 → -1.0 + /// * -1.6 → -2.0 + /// * -2.5 → -2.0 + /// * -5.5 → -5.0 + HalfDown, + + /// Round to 'nearest neighbor', if equidistant, round towards + /// nearest even digit + /// + /// * 5.5 → 6.0 + /// * 2.5 → 2.0 + /// * 1.6 → 2.0 + /// * 1.1 → 1.0 + /// * -1.1 → -1.0 + /// * -1.6 → -2.0 + /// * -2.5 → -2.0 + /// * -5.5 → -6.0 + /// + HalfEven, +} From 93abcb2a27354e98e929b54fddffaf7a6f13736c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 23 Aug 2022 23:58:00 -0400 Subject: [PATCH 066/100] Add RountingMode method for rounding a pair of digits --- src/rounding.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/rounding.rs b/src/rounding.rs index 30dc9ea..fa85c2f 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -95,3 +95,56 @@ pub enum RoundingMode { /// HalfEven, } + + +impl RoundingMode { + /// Perform the rounding operation + /// + /// Parameters + /// ---------- + /// * sign (Sign) - Sign of the number to be rounded + /// * pair (u8, u8) - The two digits in question to be rounded. + /// i.e. to round 0.345 to two places, you would pass (4, 5). + /// As decimal digits, they + /// must be less than ten! + /// * trailing_zeros (bool) - True if all digits after the pair are zero. + /// This has an effect if the right hand digit is 0 or 5. + /// + /// Returns + /// ------- + /// Returns the first number of the pair, rounded. The sign is not preserved. + /// + /// Examples + /// -------- + /// - To round 2341, pass in `Plus, (4, 1), true` → get 4 or 5 depending on scheme + /// - To round -0.1051, to two places: `Minus, (0, 5), false` → returns either 0 or 1 + /// - To round -0.1, pass in `true, (0, 1)` → returns either 0 or 1 + /// + /// Calculation of pair of digits from full number, and the replacement of that number + /// should be handled separately + /// + pub fn round_pair(&self, sign: Sign, pair: (u8, u8), trailing_zeros: bool) -> u8 { + use self::RoundingMode::*; + use stdlib::cmp::Ordering::*; + + let (lhs, rhs) = pair; + // if all zero after digit, never round + if rhs == 0 && trailing_zeros { + return lhs; + } + let up = lhs + 1; + let down = lhs; + match (*self, rhs.cmp(&5)) { + (Up, _) => up, + (Down, _) => down, + (Floor, _) => if sign == Sign::Minus { up } else { down }, + (Ceiling, _) => if sign == Sign::Minus { down } else { up }, + (_, Less) => down, + (_, Greater) => up, + (_, Equal) if !trailing_zeros => up, + (HalfUp, Equal) => up, + (HalfDown, Equal) => down, + (HalfEven, Equal) => if lhs % 2 == 0 { down } else { up }, + } + } +} From 35bd3d8ecb7f41786e074a1a83166c09dac2af1c Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 14:26:38 -0400 Subject: [PATCH 067/100] Add tests for RoundingMode::round_pair --- src/rounding.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/rounding.rs b/src/rounding.rs index fa85c2f..4ee5e81 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -148,3 +148,110 @@ impl RoundingMode { } } } + + +#[cfg(test)] +#[allow(non_snake_case)] +mod test_round_pair { + use paste::paste; + use super::*; + + macro_rules! impl_test { + ( $($mode:ident),+ => $expected:literal) => { + $( + paste! { + #[test] + fn [< mode_ $mode >]() { + let (pair, sign, trailing_zeros) = test_input(); + let mode = self::RoundingMode::$mode; + let result = mode.round_pair(sign, pair, trailing_zeros); + assert_eq!(result, $expected); + } + } + )* + } + } + + macro_rules! define_test_input { + ( - $lhs:literal, $rhs:literal $($t:tt)* ) => { + define_test_input!(sign=Sign::Minus, pair=($lhs, $rhs) $($t)*); + }; + ( $lhs:literal, $rhs:literal $($t:tt)*) => { + define_test_input!(sign=Sign::Plus, pair=($lhs, $rhs) $($t)*); + }; + ( sign=$sign:expr, $lhs:literal, $rhs:literal $($t:tt)* ) => { + define_test_input!(sign=$sign, pair=($lhs, $rhs) $($t)*); + }; + ( sign=$sign:expr, pair=$pair:expr) => { + define_test_input!(sign=$sign, pair=$pair, trailing_zeros=false); + }; + ( sign=$sign:expr, pair=$pair:expr, 000...) => { + define_test_input!(sign=$sign, pair=$pair, trailing_zeros=true); + }; + ( sign=$sign:expr, pair=$pair:expr, trailing_zeros=$trailing_zeros:literal) => { + fn test_input() -> ((u8, u8), Sign, bool) { ($pair, $sign, $trailing_zeros) } + }; + } + + mod case_0_1 { + use super::*; + + define_test_input!( 0, 1 ); + + impl_test!(Up, Ceiling => 1); + impl_test!(Down, Floor, HalfUp, HalfDown, HalfEven => 0); + } + + mod case_9_5 { + use super::*; + + define_test_input!( 9, 5 ); + + impl_test!(Up, Ceiling, HalfDown, HalfUp, HalfEven => 10); + impl_test!(Down, Floor => 9); + } + + mod case_9_5_0000 { + use super::*; + + define_test_input!( 9, 5, 000...); + + impl_test!(Up, Ceiling, HalfUp, HalfEven => 10); + impl_test!(Down, Floor, HalfDown => 9); + } + + mod case_8_5_0000 { + use super::*; + + define_test_input!( 8, 5, 000...); + + impl_test!(Up, Ceiling, HalfUp => 9); + impl_test!(Down, Floor, HalfDown, HalfEven => 8); + } + + mod case_neg_4_3 { + use super::*; + + define_test_input!(- 4, 3 ); + + impl_test!(Up, Floor => 5); + impl_test!(Down, Ceiling, HalfUp, HalfDown, HalfEven => 4); + } + + mod case_neg_6_5_000 { + use super::*; + + define_test_input!( -6, 5, 000... ); + + impl_test!(Up, Floor, HalfUp => 7); + impl_test!(Down, Ceiling, HalfDown, HalfEven => 6); + } + + mod case_neg_2_0_000 { + use super::*; + + define_test_input!( -2, 0, 000... ); + + impl_test!(Up, Down, Ceiling, Floor, HalfUp, HalfDown, HalfEven => 2); + } +} From 086b42b6b87395159cca74a42433d445db1f3fe9 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 17:50:56 -0400 Subject: [PATCH 068/100] Add method round_u32 to RoundingMode --- src/rounding.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/rounding.rs b/src/rounding.rs index 4ee5e81..be4b23d 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -1,6 +1,7 @@ //! Rounding structures and subroutines use crate::Sign; +use stdlib; /// Determines how to calculate the last digit of the number /// @@ -147,6 +148,47 @@ impl RoundingMode { (HalfEven, Equal) => if lhs % 2 == 0 { down } else { up }, } } + + /// Round value at particular digit, returning replacement digit + /// + /// Parameters + /// ---------- + /// * at_digit (NonZeroU8) - 0-based index of digit at which to round. + /// 0 would be the first digit, and would + /// + /// * sign (Sign) - Sign of the number to be rounded + /// * value (u32) - The number containing digits to be rounded. + /// * trailing_zeros (bool) - True if all digits after the value are zero. + /// + /// Returns + /// ------- + /// Returns the first number of the pair, rounded. The sign is not preserved. + /// + /// Examples + /// -------- + /// - To round 823418, at digit-index 3: `3, Plus, 823418, true` → 823000 or 824000, depending on scheme + /// - To round -100205, at digit-index 1: `1, Minus, 100205, true` → 100200 or 100210 + /// + /// Calculation of pair of digits from full number, and the replacement of that number + /// should be handled separately + /// + pub fn round_u32(&self, at_digit: stdlib::num::NonZeroU8, sign: Sign, value: u32, trailing_zeros: bool) -> u32 { + let shift = 10u32.pow(at_digit.get() as u32 - 1); + let splitter = shift * 10; + + // split 'value' into high and low + let (top, bottom) = num_integer::div_rem(value, splitter); + let lhs = (top % 10) as u8; + let (rhs, remainder) = num_integer::div_rem(bottom, shift); + let pair = (lhs, rhs as u8); + let rounded = self.round_pair(sign, pair, trailing_zeros && remainder == 0); + + // replace low digit with rounded value + let full = top - lhs as u32 + rounded as u32; + + // shift rounded value back to position + full * splitter + } } From 04d6ec09a5ab2173ffb3ddfffc1e5f6216d0ee76 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 27 May 2023 18:40:36 -0400 Subject: [PATCH 069/100] Add test_round_u32 --- src/rounding.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/rounding.rs b/src/rounding.rs index be4b23d..2c2264d 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -297,3 +297,71 @@ mod test_round_pair { impl_test!(Up, Down, Ceiling, Floor, HalfUp, HalfDown, HalfEven => 2); } } + + +#[cfg(test)] +#[allow(non_snake_case)] +mod test_round_u32 { + use paste::paste; + use super::*; + + macro_rules! impl_test { + ( $pos:literal :: $($mode:ident),+ => $expected:literal) => { + $( + paste! { + #[test] + fn [< digit_ $pos _mode_ $mode >]() { + let (value, sign, trailing_zeros) = test_input(); + let mode = self::RoundingMode::$mode; + let pos = stdlib::num::NonZeroU8::new($pos as u8).unwrap(); + let result = mode.round_u32(pos, sign, value, trailing_zeros); + assert_eq!(result, $expected); + } + } + )* + } + } + + macro_rules! define_test_input { + ( - $value:literal $($t:tt)* ) => { + define_test_input!(sign=Sign::Minus, value=$value $($t)*); + }; + ( $value:literal $($t:tt)* ) => { + define_test_input!(sign=Sign::Plus, value=$value $($t)*); + }; + ( sign=$sign:expr, value=$value:literal ) => { + define_test_input!(sign=$sign, value=$value, trailing_zeros=false); + }; + ( sign=$sign:expr, value=$value:literal 000... ) => { + define_test_input!(sign=$sign, value=$value, trailing_zeros=true); + }; + ( sign=$sign:expr, value=$value:expr, trailing_zeros=$trailing_zeros:literal ) => { + fn test_input() -> (u32, Sign, bool) { ($value, $sign, $trailing_zeros) } + }; + } + + mod case_13950000_000 { + use super::*; + + define_test_input!( 13950000 000... ); + + impl_test!(3 :: Up => 13950000); + impl_test!(5 :: Up, Ceiling, HalfUp, HalfEven => 14000000); + impl_test!(5 :: Down, HalfDown => 13900000); + } + + mod case_neg_35488622 { + use super::*; + + define_test_input!( - 35488622 ); + + impl_test!(1 :: Up => 35488630); + impl_test!(1 :: Down => 35488620); + impl_test!(2 :: Up => 35488700); + impl_test!(2 :: Down => 35488600); + impl_test!(7 :: Up, Floor => 40000000); + impl_test!(7 :: Down, Ceiling => 30000000); + impl_test!(8 :: Up => 100000000); + impl_test!(8 :: Down => 0); + } +} From 2128c9e4165ea9ac78553b36478f062da0ca63db Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 31 May 2023 01:26:18 -0400 Subject: [PATCH 070/100] Fix stdlib usage in bigdecimal_serde --- src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eaec869..55e8884 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2043,9 +2043,7 @@ impl ToBigInt for BigDecimal { /// Tools to help serializing/deserializing `BigDecimal`s #[cfg(feature = "serde")] mod bigdecimal_serde { - use stdlib::{fmt, TryFrom, FromStr}; - - use super::BigDecimal; + use super::*; use serde::{de, ser}; #[allow(unused_imports)] @@ -2123,8 +2121,6 @@ mod bigdecimal_serde { #[test] fn test_serde_serialize() { - use std::str::FromStr; - let vals = vec![ ("1.0", "1.0"), ("0.5", "0.5"), @@ -2150,8 +2146,6 @@ mod bigdecimal_serde { #[test] fn test_serde_deserialize_str() { - use std::str::FromStr; - let vals = vec![ ("1.0", "1.0"), ("0.5", "0.5"), From 5c2a8198dcc707b36904a7345e256e2fc1c51713 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 2 Jun 2023 02:09:18 -0400 Subject: [PATCH 071/100] Enable coverage reports in circle-ci --- .circleci/config.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc9a4ed..d740dbb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,7 +11,7 @@ jobs: default: "1.69.0" debian-version: type: string - default: "bullseye" + default: "buster" rust-features: type: string default: "" @@ -19,6 +19,7 @@ jobs: - image: rust:<< parameters.rust-version >>-<< parameters.debian-version >> environment: RUSTFLAGS: '-D warnings' + CARGO_NET_GIT_FETCH_WITH_CLI: "true" steps: - checkout - run: @@ -92,15 +93,17 @@ workflows: rust-version: - "1.43.1" - "1.54.0" - debian-version: - - "buster" - build-and-test: name: build-and-test:latest + debian-version: "bullseye" - build-and-test: matrix: parameters: + rust-version: + - "1.43.1" + - "1.69.0" rust-features: - "--features='serde'" - "--features='serde,string-only'" @@ -109,6 +112,7 @@ workflows: name: build-and-test:no-default-features rust-features: "--no-default-features" - #- upload-coverage: - # requires: - # - build-and-test:latest + - upload-coverage: + rust-version: "1.69.0" + requires: + - build-and-test:latest From 2be5eed4b71c98e478d2da53cd9aab12e9cd36e0 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 2 Jun 2023 20:13:19 -0400 Subject: [PATCH 072/100] Add (disabled) rustfmt config --- .rustfmt.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 33c0ae2..78b231b 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,3 +1,6 @@ - +disable_all_formatting = true +use_small_heuristics = "Off" +reorder_modules = false reorder_imports = false -max_width = 120 +# imports_layout = "HorizontalVertical" +# generics = "Visual" From 52d553bfcdf7403e961e12264ea482a1d4a68ea7 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 18 Apr 2023 22:32:00 -0400 Subject: [PATCH 073/100] Add benchmark fields and dependencies to Cargo.toml --- Cargo.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9925b59..a2bc4f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,10 @@ keywords = [ "no-std", ] license = "MIT/Apache-2.0" +autobenches = false + +[lib] +bench = false [dependencies] libm = "0.2.6" @@ -28,8 +32,15 @@ serde = { version = "1.0", optional = true, default-features = false } paste = "1" serde_json = "1.0" siphasher = { version = "0.3.10", default-features = false } +criterion = { version = "0.4", features = [ "html_reports" ] } +oorandom = { version = "11.1.3" } +lazy_static = { version = "1" } [features] default = ["std"] string-only = [] std = ["num-bigint/std", "num-integer/std", "num-traits/std"] + +[[bench]] +name = "arithmetic" +harness = false From a865601b9334e022f8d5d29bbcf3ff4881dfdf9e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 31 May 2023 21:49:32 -0400 Subject: [PATCH 074/100] Create arithmetic benches --- benches/arithmetic.rs | 335 ++++++++++++++++++++++++++++++++++++++++++ benches/common.rs | 39 +++++ 2 files changed, 374 insertions(+) create mode 100644 benches/arithmetic.rs create mode 100644 benches/common.rs diff --git a/benches/arithmetic.rs b/benches/arithmetic.rs new file mode 100644 index 0000000..222c40a --- /dev/null +++ b/benches/arithmetic.rs @@ -0,0 +1,335 @@ +//! Benchmarks for arithmetic opertaion + +extern crate criterion; +extern crate bigdecimal; +extern crate oorandom; + +use std::fs::File; +use std::time::Duration; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use bigdecimal::BigDecimal; + +mod common; +use common::*; + +criterion_main!( + arithmetic, + arithmetic_bulk, +); + +criterion_group!( + name = arithmetic_bulk; + config = Criterion::default() + .measurement_time(Duration::from_secs(7)) + .sample_size(700); + targets = + criterion_benchmark, + datafile_1f633481a742923ab65855c90157bbf7::addition_bulk, +); + +criterion_group!( + name = arithmetic; + config = Criterion::default() + .sample_size(300); + targets = + datafile_9a08ddaa6ce6693cdd7b8a524e088bd0::arithmetic, + datafile_1f633481a742923ab65855c90157bbf7::addition, +); + + +fn make_random_pairs(decs: &[BigDecimal], seed: u64) -> Vec<(&BigDecimal, &BigDecimal)> { + let mut cartesian_pairs = decs + .iter() + .enumerate() + .flat_map(|(i, x)| { + decs.iter().skip(i+1).map(move |y| (x, y)) + }).collect::>(); + + // random number generator from random seed + let mut rng = oorandom::Rand32::new(seed); + + for i in (1..cartesian_pairs.len()).rev() { + let j = rng.rand_u32() as usize % i; + cartesian_pairs.swap(i, j); + } + + cartesian_pairs +} + +fn bench_addition_pairwise(name: &str, c: &mut Criterion, decs: &[BigDecimal]) { + let x_iter = decs.iter().step_by(2); + let y_iter = decs.iter().skip(1).step_by(2); + let pair_iter = std::iter::zip(x_iter, y_iter); + + c.bench_function( + name, + |b| b.iter_batched( + || { + pair_iter.clone() + }, + |pairs| { + for (x, y) in pairs { + black_box(x + y); + } + }, + criterion::BatchSize::SmallInput)); +} + + +fn bench_addition_pairs( + name: &str, + c: &mut Criterion, + dec_pairs: &[(&BigDecimal, &BigDecimal)], +) { + let pair_iter = dec_pairs.iter().cycle(); + + let mut iter_count = 0; + c.bench_function( + name, + |b| b.iter_batched( + || { + iter_count += 1; + pair_iter.clone().skip(iter_count).next().unwrap() + }, + |(x, y)| { + black_box(*x + *y); + }, + criterion::BatchSize::SmallInput)); +} + + +fn bench_addition_pairs_bulk( + name: &str, + c: &mut Criterion, + chunk_size: usize, + dec_pairs: &[(&BigDecimal, &BigDecimal)], +) { + let pair_iter = dec_pairs.iter(); + + let mut iter_count = 0; + c.bench_function( + name, + |b| b.iter_batched( + || { + let skip_by = chunk_size * iter_count; + iter_count += 1; + pair_iter.clone().skip(skip_by).take(chunk_size) + }, + |pairs| { + for (x, y) in pairs { + black_box(*x + *y); + } + }, + criterion::BatchSize::SmallInput)); +} + +fn bench_multiplication_pairs( + name: &str, + c: &mut Criterion, + dec_pairs: &[(&BigDecimal, &BigDecimal)], +) { + let pair_iter = dec_pairs.iter().cycle(); + + let mut iter_count = 0; + c.bench_function( + name, + |b| b.iter_batched( + || { + iter_count += 1; + pair_iter.clone().skip(iter_count).next().unwrap() + }, + |(x, y)| { + black_box(*x * *y); + }, + criterion::BatchSize::SmallInput)); +} + +fn bench_inverse( + name: &str, + c: &mut Criterion, + decs: &[BigDecimal], +) { + let mut idx = 0; + c.bench_function( + name, + |b| b.iter_batched( + || { + idx += 1; + if idx == decs.len() { + idx = 0; + } + &decs[idx] + }, + |x| { + black_box(x.inverse()); + }, + criterion::BatchSize::SmallInput)); +} + + +mod datafile_1f633481a742923ab65855c90157bbf7 { + use super::*; + + fn get_bigdecimals() -> Vec { + let file = File::open("../bigdecimal-test-inputs/random-bigdecimals-1f633481a742923ab65855c90157bbf7.txt") + .expect("Could not load random datafile 1f633481a742923ab65855c90157bbf7"); + super::read_bigdecimals(file) + } + + pub fn addition(c: &mut Criterion) { + let decs = get_bigdecimals(); + let cartesian_pairs = make_random_pairs(&decs, 7238269155957952517_u64); + + bench_addition_pairwise( + "addition-pairwise-1f633481a742923ab65855c90157bbf7", c, &decs + ); + + bench_addition_pairs( + "addition-random-1f633481a742923ab65855c90157bbf7", c, &cartesian_pairs + ); + } + + pub fn addition_bulk(c: &mut Criterion) { + let decs = get_bigdecimals(); + let cartesian_pairs = make_random_pairs(&decs, 7238269155957952517_u64); + bench_addition_pairs_bulk( + "addition-random-1f633481a742923ab65855c90157bbf7-100", c, 100, &cartesian_pairs + ); + } +} + +mod datafile_9a08ddaa6ce6693cdd7b8a524e088bd0 { + use super::*; + + fn get_bigdecimals() -> Vec { + let file = File::open("../bigdecimal-test-inputs/random-bigdecimals-9a08ddaa6ce6693cdd7b8a524e088bd0.txt") + .expect("Could not load random datafile 9a08ddaa6ce6693cdd7b8a524e088bd0"); + super::read_bigdecimals(file) + } + + pub fn arithmetic(c: &mut Criterion) { + let decs = get_bigdecimals(); + let cartesian_pairs = make_random_pairs(&decs, 7238269155957952517_u64); + + bench_addition_pairwise( + "addition-pairwise-9a08ddaa6ce6693cdd7b8a524e088bd0", c, &decs + ); + + bench_addition_pairs( + "addition-random-9a08ddaa6ce6693cdd7b8a524e088bd0", c, &cartesian_pairs + ); + + bench_multiplication_pairs( + "multiplication-random-9a08ddaa6ce6693cdd7b8a524e088bd0", c, &cartesian_pairs + ); + + bench_inverse( + "inverse-9a08ddaa6ce6693cdd7b8a524e088bd0", c, &decs + ); + } +} + + +pub fn criterion_benchmark(c: &mut Criterion) { + + let file_a = File::open("../bigdecimal-test-inputs/random-values.txt").expect("Could not load bigdecimal-a"); + let file_b = File::open("../bigdecimal-test-inputs/random-values-b.txt").expect("Could not load bigdecimal-b"); + + let decs_a = read_bigdecimals(file_a); + let decs_b = read_bigdecimals(file_b); + + let mut decimal_values = Vec::with_capacity(decs_a.len() + decs_b.len()); + for dec in decs_a.iter().chain(decs_b.iter()) { + decimal_values.push(dec) + } + + let mut decimal_pairs = Vec::with_capacity(decs_a.len() * decs_b.len()); + for a in decs_a.iter() { + for b in decs_b.iter() { + decimal_pairs.push((a, b)); + } + } + + let mut random_decimal = RandomIterator::new(&decimal_values); + // let mut random_decimal = decimal_values.iter().cycle(); + let mut random_pairs = RandomIterator::new(&decimal_pairs); + // let mut random_pairs = decimal_pairs.iter().cycle().map(|d| *d); + + c.bench_function( + "addition", + |b| b.iter_batched( + || { + random_pairs.next() + }, + |(a, b)| { + black_box(a + b); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "subtraction", + |b| b.iter_batched( + || { + random_pairs.next() + }, + |(a, b)| { + black_box(a - b); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "multiplication", + |b| b.iter_batched( + || { + random_pairs.next() + }, + |(a, b)| { + black_box(a * b); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "division", + |b| b.iter_batched( + || { + random_pairs.next() + }, + |(a, b)| { + black_box(a / b); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "square", + |b| b.iter_batched( + || { + random_decimal.next() + }, + |a| { + black_box(a.square()); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "cube", + |b| b.iter_batched( + || { + random_decimal.next() + }, + |a| { + black_box(a.cube()); + }, + criterion::BatchSize::SmallInput)); + + c.bench_function( + "sqrt", + |b| b.iter_batched( + || { + random_decimal.next() + }, + |a| { + black_box(a.sqrt()); + }, + criterion::BatchSize::SmallInput)); +} diff --git a/benches/common.rs b/benches/common.rs new file mode 100644 index 0000000..272c639 --- /dev/null +++ b/benches/common.rs @@ -0,0 +1,39 @@ +// to be included by benches +use std::fs::File; +use std::io::BufReader; +use bigdecimal::BigDecimal; + +use std::str::FromStr; +use std::io::BufRead; + +/// Read vector of big decimals from lines in file +pub fn read_bigdecimals(file: File) -> Vec { + let buf_reader = BufReader::new(file); + buf_reader + .lines() + .map(|maybe_string| maybe_string.unwrap()) + .map(|line| BigDecimal::from_str(&line).unwrap()) + .collect() +} + +/// Iterate over vector in random order +pub struct RandomIterator<'a, T> { + v: &'a Vec, + rng: oorandom::Rand32, +} + +impl<'a, T: Copy> RandomIterator<'a, T> { + pub fn new(v: &'a Vec) -> Self { + let seed = v.as_ptr() as u64; + Self { + v: v, + rng: oorandom::Rand32::new(seed), + } + } + + pub fn next(&mut self) -> T { + let randval = self.rng.rand_u32() as usize % self.v.len(); + let idx = randval % self.v.len(); + self.v[idx] + } +} From 21a109407c52feb0446d823af3ac0167802a3155 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 31 May 2023 22:00:59 -0400 Subject: [PATCH 075/100] Add benchmark test-data directory to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1878aae..0126c8f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ target .vscode/ .*cache/ + +benches/test-data/ From 15c069d691e619732c666479fd0a8692e3367623 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 31 May 2023 23:24:42 -0400 Subject: [PATCH 076/100] Create script to fetch benchmark data --- scripts/fetch-benchmark-data.sh | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 scripts/fetch-benchmark-data.sh diff --git a/scripts/fetch-benchmark-data.sh b/scripts/fetch-benchmark-data.sh new file mode 100755 index 0000000..83af9b1 --- /dev/null +++ b/scripts/fetch-benchmark-data.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Fetch benchmark data +# +# Should this just be a makefile? +# + +TEST_DATA_DIR=benches/test-data + +CURL=$(command -v curl) +WGET=$(command -v wget) + +GITLAB_URL_PATTERN='https://gitlab.com/akubera/bigdecimal-rs/-/raw/data-files/random-test-inputs/' + +function fetch_benchmark_bigdecimal_file() { + local FILENAME="random-bigdecimals-$1.txt" + local FILEPATH=$TEST_DATA_DIR/$FILENAME + + if [ -e $FILEPATH ]; then + echo "exists: $FILEPATH" + else + local URL=${GITLAB_URL_PATTERN///$FILENAME} + echo "fetching: $FILEPATH from $URL" + + if [ $CURL ]; then + $CURL -s --fail -L $URL -o "$FILEPATH" + elif [ $WGET ]; then + $WGET --quiet $URL -O "$FILEPATH" + else + echo "No supported fetching program" + fi + fi +} + +mkdir -p $TEST_DATA_DIR + +fetch_benchmark_bigdecimal_file "1f633481a742923ab65855c90157bbf7" +fetch_benchmark_bigdecimal_file "9a08ddaa6ce6693cdd7b8a524e088bd0" + +fetch_benchmark_bigdecimal_file "4be58192272b15fc67573b39910837d0" +fetch_benchmark_bigdecimal_file "a329e61834832d89593b29f12510bdc8" From ab56c9a732a0fee10decaa6f936e9446021300d1 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 3 Jun 2023 01:22:26 -0400 Subject: [PATCH 077/100] Update latest rust version to 1.70 and add benchmark job in gitlab-ci --- .gitlab-ci.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 936224c..47067b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -85,24 +85,24 @@ cargo:test-1.54: <<: *cargo-test-script -cargo:build-1.68: +cargo:build-1.70: stage: build - image: "akubera/rust-grcov:1.68.2-bullseye" + image: "akubera/rust-grcov:1.70.0-bullseye" <<: *cargo-build-script -cargo:test-1.68: +cargo:test-1.70: stage: test needs: - - "cargo:build-1.68" - image: "akubera/rust-grcov:1.68.2-bullseye" + - "cargo:build-1.70" + image: "akubera/rust-grcov:1.70.0-bullseye" <<: *cargo-test-script coverage-test: stage: test needs: - - "cargo:test-1.68" - image: "akubera/rust-grcov:1.68.2-bullseye" + - "cargo:test-1.70" + image: "akubera/rust-grcov:1.70.0-bullseye" variables: CARGO_NET_GIT_FETCH_WITH_CLI: 'true' @@ -129,6 +129,24 @@ coverage-test: path: cobertura.xml +cargo:benchmark: + stage: test + needs: + - "cargo:test-1.70" + image: "akubera/bigdecimal-benchmark-base:1.70.0-bullseye" + when: manual + allow_failure: true + cache: [] + variables: + CARGO_HOME: /usr/local/cargo + script: + - cargo bench + artifacts: + expose_as: 'criterion-benchmark-report' + paths: + - target/criterion + + cargo-publish: stage: deploy image: "rust:latest" From 6d8cf11d94c519c8f7ac965f428588e9b3d731ff Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 3 Jun 2023 09:42:20 -0400 Subject: [PATCH 078/100] Make the benchmarking dependencies optional in Cargo.toml --- .gitlab-ci.yml | 4 +++- Cargo.toml | 8 +++++--- scripts/benchmark-bigdecimal | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100755 scripts/benchmark-bigdecimal diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47067b1..35f2af9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -139,12 +139,14 @@ cargo:benchmark: cache: [] variables: CARGO_HOME: /usr/local/cargo + BENCHMARK_EXTRAS: 1 script: - - cargo bench + - scripts/benchmark-bigdecimal artifacts: expose_as: 'criterion-benchmark-report' paths: - target/criterion + - "*.html" cargo-publish: diff --git a/Cargo.toml b/Cargo.toml index a2bc4f9..1378c74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,9 +32,11 @@ serde = { version = "1.0", optional = true, default-features = false } paste = "1" serde_json = "1.0" siphasher = { version = "0.3.10", default-features = false } -criterion = { version = "0.4", features = [ "html_reports" ] } -oorandom = { version = "11.1.3" } -lazy_static = { version = "1" } +# The following dev-dependencies are only required for benchmarking +# (use the `benchmark-bigdecimal` script to uncomment these and run benchmarks) +# BENCH: criterion = { version = "0.4", features = [ "html_reports" ] } +# BENCH: oorandom = { version = "11.1.3" } +# BENCH: lazy_static = { version = "1" } [features] default = ["std"] diff --git a/scripts/benchmark-bigdecimal b/scripts/benchmark-bigdecimal new file mode 100755 index 0000000..6546174 --- /dev/null +++ b/scripts/benchmark-bigdecimal @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Run Criterion Benchmarks +# + +INDEX_HTML=" + +

Benchmark Results

+

Criterion Report: Report

+" + +# enable bench-only dependencies in Cargo +sed -i.bak -e 's/# BENCH: //' Cargo.toml + +# Run criterion +cargo bench "$@" + +# Restore Cargo.toml with backup +mv Cargo.toml.bak Cargo.toml + + +# store extra things for the benchmarking report +if [ -v BENCHMARK_EXTRAS ]; then + echo $INDEX_HTML > index.html +fi From af806370daec42c3ffc64ad3f41cafff6d11b25a Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 3 Jun 2023 20:36:53 -0400 Subject: [PATCH 079/100] Add PDF plots to generated index.html in benchmark-bigdecimal --- scripts/benchmark-bigdecimal | 43 +++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/scripts/benchmark-bigdecimal b/scripts/benchmark-bigdecimal index 6546174..04c08f2 100755 --- a/scripts/benchmark-bigdecimal +++ b/scripts/benchmark-bigdecimal @@ -3,12 +3,6 @@ # Run Criterion Benchmarks # -INDEX_HTML=" - -

Benchmark Results

-

Criterion Report: Report

-" - # enable bench-only dependencies in Cargo sed -i.bak -e 's/# BENCH: //' Cargo.toml @@ -20,6 +14,39 @@ mv Cargo.toml.bak Cargo.toml # store extra things for the benchmarking report -if [ -v BENCHMARK_EXTRAS ]; then - echo $INDEX_HTML > index.html +if [ ! -z "$BENCHMARK_EXTRAS" ]; then +cat < index.html + + + + +BigDecimal Benchmark Results + + + +${CI_COMMIT_SHA} +

BigDecimal Benchmark Results

+

Criterion Report: Report

+EOF + +# Add svg plots to index html +find target/criterion -name 'pdf.svg' -type f -print0 | +sort -z | +while read -d $'\0' svg_file +do + name=$(echo $svg_file | cut -d '/' -f 3) + + sample_datafile=target/criterion/$name/new/sample.json + if [ -f $sample_datafile ]; then + echo "

$name" >> index.html + else + echo "

$name

" >> index.html + fi + echo "" >> index.html +done + fi From 8ec0326529dc2f919b3183ff30978dd65d9c212e Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sat, 3 Jun 2023 02:01:59 -0400 Subject: [PATCH 080/100] Add macros and BigDecimal building functions to benches::common --- benches/arithmetic.rs | 33 ++++++++++++++++++----------- benches/common.rs | 48 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/benches/arithmetic.rs b/benches/arithmetic.rs index 222c40a..b6b3299 100644 --- a/benches/arithmetic.rs +++ b/benches/arithmetic.rs @@ -3,6 +3,9 @@ extern crate criterion; extern crate bigdecimal; extern crate oorandom; +extern crate lazy_static; + +use lazy_static::lazy_static; use std::fs::File; use std::time::Duration; @@ -10,6 +13,7 @@ use std::time::Duration; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use bigdecimal::BigDecimal; +#[macro_use] mod common; use common::*; @@ -171,10 +175,10 @@ fn bench_inverse( mod datafile_1f633481a742923ab65855c90157bbf7 { use super::*; + fn get_bigdecimals() -> Vec { - let file = File::open("../bigdecimal-test-inputs/random-bigdecimals-1f633481a742923ab65855c90157bbf7.txt") - .expect("Could not load random datafile 1f633481a742923ab65855c90157bbf7"); - super::read_bigdecimals(file) + let file = open_benchmark_data_file!("random-bigdecimals-1f633481a742923ab65855c90157bbf7.txt"); + super::read_bigdecimal_file(file) } pub fn addition(c: &mut Criterion) { @@ -202,10 +206,14 @@ mod datafile_1f633481a742923ab65855c90157bbf7 { mod datafile_9a08ddaa6ce6693cdd7b8a524e088bd0 { use super::*; - fn get_bigdecimals() -> Vec { - let file = File::open("../bigdecimal-test-inputs/random-bigdecimals-9a08ddaa6ce6693cdd7b8a524e088bd0.txt") - .expect("Could not load random datafile 9a08ddaa6ce6693cdd7b8a524e088bd0"); - super::read_bigdecimals(file) + const SRC: &'static str = include_benchmark_data_file!("random-bigdecimals-9a08ddaa6ce6693cdd7b8a524e088bd0.txt"); + + lazy_static! { + static ref BIG_DECIMALS: Vec = super::collect_bigdecimals(SRC); + } + + fn get_bigdecimals<'a>() -> &'a [BigDecimal] { + BIG_DECIMALS.as_slice() } pub fn arithmetic(c: &mut Criterion) { @@ -232,12 +240,13 @@ mod datafile_9a08ddaa6ce6693cdd7b8a524e088bd0 { pub fn criterion_benchmark(c: &mut Criterion) { + let src_a = include_benchmark_data_file!("random-bigdecimals-a329e61834832d89593b29f12510bdc8.txt"); + let src_b = include_benchmark_data_file!("random-bigdecimals-4be58192272b15fc67573b39910837d0.txt"); - let file_a = File::open("../bigdecimal-test-inputs/random-values.txt").expect("Could not load bigdecimal-a"); - let file_b = File::open("../bigdecimal-test-inputs/random-values-b.txt").expect("Could not load bigdecimal-b"); - - let decs_a = read_bigdecimals(file_a); - let decs_b = read_bigdecimals(file_b); + let decs_a = collect_bigdecimals(src_a); + let decs_b = collect_bigdecimals(src_b); + assert_ne!(decs_a.len(), 0); + assert_ne!(decs_b.len(), 0); let mut decimal_values = Vec::with_capacity(decs_a.len() + decs_b.len()); for dec in decs_a.iter().chain(decs_b.iter()) { diff --git a/benches/common.rs b/benches/common.rs index 272c639..75d523b 100644 --- a/benches/common.rs +++ b/benches/common.rs @@ -1,4 +1,5 @@ -// to be included by benches +//! common routines to be included by benches + use std::fs::File; use std::io::BufReader; use bigdecimal::BigDecimal; @@ -6,17 +7,50 @@ use bigdecimal::BigDecimal; use std::str::FromStr; use std::io::BufRead; +macro_rules! resolve_benchmark_data_file { + ( $filename:literal ) => { + concat!(env!("BIGDECIMAL_BENCHMARK_DATA_PATH"), "/", $filename) + } +} + +macro_rules! open_benchmark_data_file { + ( $filename:literal ) => {{ + let path = resolve_benchmark_data_file!($filename); + File::open(path).expect(&format!(concat!("Could not load random datafile ", $filename, " from path {:?}"), path)) + }}; +} + + +macro_rules! include_benchmark_data_file { + ( $filename:literal ) => {{ + include_str!( resolve_benchmark_data_file!($filename) ) + }}; +} + /// Read vector of big decimals from lines in file -pub fn read_bigdecimals(file: File) -> Vec { - let buf_reader = BufReader::new(file); - buf_reader +pub fn read_bigdecimal_file(file: File) -> Vec { + read_bigdecimals(BufReader::new(file)) +} + +/// Read bigdecaiml from buffered reader +pub fn read_bigdecimals(reader: R) -> Vec +{ + reader .lines() .map(|maybe_string| maybe_string.unwrap()) .map(|line| BigDecimal::from_str(&line).unwrap()) .collect() } -/// Iterate over vector in random order +/// Collect big-decimals from lines in string +pub fn collect_bigdecimals(src: &str) -> Vec { + src.lines() + .map(|line| BigDecimal::from_str(&line).unwrap()) + .collect() +} + + +/// Randomly iterates through items in vector pub struct RandomIterator<'a, T> { v: &'a Vec, rng: oorandom::Rand32, @@ -25,6 +59,10 @@ pub struct RandomIterator<'a, T> { impl<'a, T: Copy> RandomIterator<'a, T> { pub fn new(v: &'a Vec) -> Self { let seed = v.as_ptr() as u64; + Self::new_with_seed(v, seed) + } + + pub fn new_with_seed(v: &'a Vec, seed: u64) -> Self { Self { v: v, rng: oorandom::Rand32::new(seed), From 85a8a87d4be228f5a095d512885c7a3d18dc9423 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 4 Jun 2023 13:51:10 -0400 Subject: [PATCH 081/100] Restructure rounding tests --- src/rounding.rs | 166 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 132 insertions(+), 34 deletions(-) diff --git a/src/rounding.rs b/src/rounding.rs index 2c2264d..a1053d5 100644 --- a/src/rounding.rs +++ b/src/rounding.rs @@ -215,22 +215,19 @@ mod test_round_pair { } macro_rules! define_test_input { - ( - $lhs:literal, $rhs:literal $($t:tt)* ) => { - define_test_input!(sign=Sign::Minus, pair=($lhs, $rhs) $($t)*); + ( - $lhs:literal . $rhs:literal $($t:tt)* ) => { + define_test_input!(sign=Sign::Minus, pair=($lhs, $rhs), $($t)*); }; - ( $lhs:literal, $rhs:literal $($t:tt)*) => { - define_test_input!(sign=Sign::Plus, pair=($lhs, $rhs) $($t)*); + ( $lhs:literal . $rhs:literal $($t:tt)*) => { + define_test_input!(sign=Sign::Plus, pair=($lhs, $rhs), $($t)*); }; - ( sign=$sign:expr, $lhs:literal, $rhs:literal $($t:tt)* ) => { - define_test_input!(sign=$sign, pair=($lhs, $rhs) $($t)*); + ( sign=$sign:expr, pair=$pair:expr, ) => { + define_test_input!(sign=$sign, pair=$pair, trailing_zeros=true); }; - ( sign=$sign:expr, pair=$pair:expr) => { + ( sign=$sign:expr, pair=$pair:expr, 000x ) => { define_test_input!(sign=$sign, pair=$pair, trailing_zeros=false); }; - ( sign=$sign:expr, pair=$pair:expr, 000...) => { - define_test_input!(sign=$sign, pair=$pair, trailing_zeros=true); - }; - ( sign=$sign:expr, pair=$pair:expr, trailing_zeros=$trailing_zeros:literal) => { + ( sign=$sign:expr, pair=$pair:expr, trailing_zeros=$trailing_zeros:literal ) => { fn test_input() -> ((u8, u8), Sign, bool) { ($pair, $sign, $trailing_zeros) } }; } @@ -238,64 +235,164 @@ mod test_round_pair { mod case_0_1 { use super::*; - define_test_input!( 0, 1 ); + define_test_input!(0 . 1); impl_test!(Up, Ceiling => 1); impl_test!(Down, Floor, HalfUp, HalfDown, HalfEven => 0); } - mod case_9_5 { + mod case_neg_0_1 { + use super::*; + + define_test_input!(-0 . 1); + + impl_test!(Up, Floor => 1); + impl_test!(Down, Ceiling, HalfUp, HalfDown, HalfEven => 0); + } + + mod case_0_5 { + use super::*; + + define_test_input!( 0 . 5 ); + + impl_test!(Up, Ceiling, HalfUp => 1); + impl_test!(Down, Floor, HalfDown, HalfEven => 0); + } + + mod case_neg_0_5 { + use super::*; + + define_test_input!(-0 . 5); + + impl_test!(Up, Floor, HalfUp => 1); + impl_test!(Down, Ceiling, HalfDown, HalfEven => 0); + } + + mod case_0_5_000x { + use super::*; + + // ...000x indicates a non-zero trailing digit; affects behavior of rounding N.0 and N.5 + define_test_input!(0 . 5 000x); + + impl_test!(Up, Ceiling, HalfUp, HalfDown, HalfEven => 1); + impl_test!(Down, Floor => 0); + } + + mod case_neg_0_5_000x { + use super::*; + + define_test_input!(-0 . 5 000x); + + impl_test!(Up, Floor, HalfUp, HalfDown, HalfEven => 1); + impl_test!(Down, Ceiling => 0); + } + + mod case_0_7 { use super::*; - define_test_input!( 9, 5 ); + define_test_input!(0 . 7); + + impl_test!(Up, Ceiling, HalfUp, HalfDown, HalfEven => 1); + impl_test!(Down, Floor => 0); + } + + mod case_neg_0_7 { + use super::*; + + define_test_input!(-0 . 7); + + impl_test!(Up, Floor, HalfUp, HalfDown, HalfEven => 1); + impl_test!(Down, Ceiling => 0); + } + + mod case_neg_4_3_000x { + use super::*; + + define_test_input!(-4 . 3 000x); + + impl_test!(Up, Floor => 5); + impl_test!(Down, Ceiling, HalfUp, HalfDown, HalfEven => 4); + } + + + mod case_9_5_000x { + use super::*; + + define_test_input!(9 . 5 000x); impl_test!(Up, Ceiling, HalfDown, HalfUp, HalfEven => 10); impl_test!(Down, Floor => 9); } - mod case_9_5_0000 { + mod case_9_5 { use super::*; - define_test_input!( 9, 5, 000...); + define_test_input!(9 . 5); impl_test!(Up, Ceiling, HalfUp, HalfEven => 10); impl_test!(Down, Floor, HalfDown => 9); } - mod case_8_5_0000 { + mod case_8_5 { use super::*; - define_test_input!( 8, 5, 000...); + define_test_input!(8 . 5); impl_test!(Up, Ceiling, HalfUp => 9); impl_test!(Down, Floor, HalfDown, HalfEven => 8); } - mod case_neg_4_3 { + mod case_neg_6_5 { use super::*; - define_test_input!(- 4, 3 ); + define_test_input!(-6 . 5); - impl_test!(Up, Floor => 5); - impl_test!(Down, Ceiling, HalfUp, HalfDown, HalfEven => 4); + impl_test!(Up, Floor, HalfUp => 7); + impl_test!(Down, Ceiling, HalfDown, HalfEven => 6); } - mod case_neg_6_5_000 { + mod case_neg_6_5_000x { use super::*; - define_test_input!( -6, 5, 000... ); + define_test_input!(-6 . 5 000x); - impl_test!(Up, Floor, HalfUp => 7); - impl_test!(Down, Ceiling, HalfDown, HalfEven => 6); + impl_test!(Up, Floor, HalfUp, HalfDown, HalfEven => 7); + impl_test!(Down, Ceiling => 6); } - mod case_neg_2_0_000 { + mod case_3_0 { use super::*; - define_test_input!( -2, 0, 000... ); + define_test_input!(3 . 0); + + impl_test!(Up, Down, Ceiling, Floor, HalfUp, HalfDown, HalfEven => 3); + } + + mod case_3_0_000x { + use super::*; + + define_test_input!(3 . 0 000x); + + impl_test!(Up, Ceiling => 4); + impl_test!(Down, Floor, HalfUp, HalfDown, HalfEven => 3); + } + + mod case_neg_2_0 { + use super::*; + + define_test_input!(-2 . 0); impl_test!(Up, Down, Ceiling, Floor, HalfUp, HalfDown, HalfEven => 2); } + + mod case_neg_2_0_000x { + use super::*; + + define_test_input!(-2 . 0 000x); + + impl_test!(Up, Floor => 3); + impl_test!(Down, Ceiling, HalfUp, HalfDown, HalfEven => 2); + } } @@ -329,10 +426,10 @@ mod test_round_u32 { ( $value:literal $($t:tt)* ) => { define_test_input!(sign=Sign::Plus, value=$value $($t)*); }; - ( sign=$sign:expr, value=$value:literal ) => { + ( sign=$sign:expr, value=$value:literal ...000x ) => { define_test_input!(sign=$sign, value=$value, trailing_zeros=false); }; - ( sign=$sign:expr, value=$value:literal 000... ) => { + ( sign=$sign:expr, value=$value:literal ) => { define_test_input!(sign=$sign, value=$value, trailing_zeros=true); }; ( sign=$sign:expr, value=$value:expr, trailing_zeros=$trailing_zeros:literal ) => { @@ -340,20 +437,21 @@ mod test_round_u32 { }; } - mod case_13950000_000 { + mod case_13950000 { use super::*; - define_test_input!( 13950000 000... ); + define_test_input!(13950000); impl_test!(3 :: Up => 13950000); impl_test!(5 :: Up, Ceiling, HalfUp, HalfEven => 14000000); impl_test!(5 :: Down, HalfDown => 13900000); } - mod case_neg_35488622 { + mod case_neg_35488622_000x { use super::*; - define_test_input!( - 35488622 ); + // ...000x indicates non-zero trailing digit + define_test_input!(-35488622 ...000x); impl_test!(1 :: Up => 35488630); impl_test!(1 :: Down => 35488620); From 479515766fe137acc4fce3e28ac306e67513c4a2 Mon Sep 17 00:00:00 2001 From: qdrs Date: Wed, 31 May 2023 00:08:09 +0800 Subject: [PATCH 082/100] override default FromPrimitive implementation for u128 and i128 --- src/lib.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 55e8884..98035b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1954,6 +1954,24 @@ impl From for BigDecimal { } } +impl From for BigDecimal { + fn from(n: i128) -> Self { + BigDecimal { + int_val: BigInt::from(n), + scale: 0, + } + } +} + +impl From for BigDecimal { + fn from(n: u128) -> Self { + BigDecimal { + int_val: BigInt::from(n), + scale: 0, + } + } +} + impl From<(BigInt, i64)> for BigDecimal { #[inline] fn from((int_val, scale): (BigInt, i64)) -> Self { @@ -2023,6 +2041,16 @@ impl FromPrimitive for BigDecimal { Some(BigDecimal::from(n)) } + #[inline] + fn from_i128(n: i128) -> Option { + Some(BigDecimal::from(n)) + } + + #[inline] + fn from_u128(n: u128) -> Option { + Some(BigDecimal::from(n)) + } + #[inline] fn from_f32(n: f32) -> Option { BigDecimal::try_from(n).ok() From 7d4c77c347cdcacdee13f38bd3a698c1f039c5e5 Mon Sep 17 00:00:00 2001 From: qdrs Date: Wed, 31 May 2023 13:02:03 +0800 Subject: [PATCH 083/100] add basic unit tests --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 98035b5..97ebd13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3307,4 +3307,14 @@ mod bigdecimal_tests { fn test_bad_string_only_decimal_and_exponent() { BigDecimal::from_str(".e4").unwrap(); } + + #[test] + fn test_from_i128() { + BigDecimal::from_i128(-368934881474191032320).unwrap(); + } + + #[test] + fn test_from_u128() { + BigDecimal::from_i128(668934881474191032320).unwrap(); + } } From 89081c7beb5e2ebd22c04b05eecabec3868dc0a9 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Fri, 2 Jun 2023 00:37:42 -0400 Subject: [PATCH 084/100] Add explicit checks to from_[iu]128 unit tests --- src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97ebd13..9ce8108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3310,11 +3310,15 @@ mod bigdecimal_tests { #[test] fn test_from_i128() { - BigDecimal::from_i128(-368934881474191032320).unwrap(); + let value = BigDecimal::from_i128(-368934881474191032320).unwrap(); + let expected = BigDecimal::from_str("-368934881474191032320").unwrap(); + assert_eq!(value, expected); } #[test] fn test_from_u128() { - BigDecimal::from_i128(668934881474191032320).unwrap(); + let value = BigDecimal::from_u128(668934881474191032320).unwrap(); + let expected = BigDecimal::from_str("668934881474191032320").unwrap(); + assert_eq!(value, expected); } } From 4fafd2b0d604aa3d3c3146b4a8a757556f106bee Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 4 Jun 2023 17:23:42 -0400 Subject: [PATCH 085/100] Replace remaining hardcoded values of max_precision --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9ce8108..de98ab1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1555,7 +1555,7 @@ impl Div for BigDecimal { }; } - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; return impl_division(self.int_val, &other.int_val, scale, max_precision); } @@ -1581,7 +1581,7 @@ impl<'a> Div<&'a BigDecimal> for BigDecimal { }; } - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; return impl_division(self.int_val, &other.int_val, scale, max_precision); } @@ -1614,7 +1614,7 @@ impl<'a, 'b> Div<&'b BigDecimal> for &'a BigDecimal { }; } - let max_precision = 100; + let max_precision = DEFAULT_PRECISION; return impl_division(num_int.clone(), den_int, scale, max_precision); } From dc83ac8196280c46ed6821c901b0cd623c3510ce Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Sun, 4 Jun 2023 18:30:02 -0400 Subject: [PATCH 086/100] Fix missing rerun-if-env-changed bug in build.rs --- build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/build.rs b/build.rs index a299729..f01fd1d 100644 --- a/build.rs +++ b/build.rs @@ -30,6 +30,7 @@ fn write_default_precision(outdir_path: &PathBuf, filename: &str) -> std::io::Re write!(default_precision_rs, "const DEFAULT_PRECISION: u64 = {};", default_prec)?; println!("cargo:rerun-if-changed={}", default_precision_rs_path.display()); + println!("cargo:rerun-if-env-changed={}", "RUST_BIGDECIMAL_DEFAULT_PRECISION"); Ok(()) } From 83abb78107a1175354f113f1e705a4b1eb1002ac Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 5 Jun 2023 01:51:52 -0400 Subject: [PATCH 087/100] Fix issues with cago:benchmark gitlab-ci job --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 35f2af9..244a1d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -139,11 +139,10 @@ cargo:benchmark: cache: [] variables: CARGO_HOME: /usr/local/cargo - BENCHMARK_EXTRAS: 1 + BENCHMARK_EXTRAS: "1" script: - scripts/benchmark-bigdecimal artifacts: - expose_as: 'criterion-benchmark-report' paths: - target/criterion - "*.html" From c291f81b5d708b0503c6f880b16bef7dfcd703bc Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 6 Jun 2023 00:25:42 -0400 Subject: [PATCH 088/100] Implement with_scale_round --- src/lib.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index de98ab1..48cf190 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,7 @@ extern crate paste; mod parsing; pub mod rounding; +pub use rounding::RoundingMode; #[inline(always)] fn ten_to_the(pow: u64) -> BigInt { @@ -240,6 +241,83 @@ impl BigDecimal { } } + /// Return a new BigDecimal after shortening the digits and rounding + pub fn with_scale_round(&self, new_scale: i64, mode: RoundingMode) -> BigDecimal { + use stdlib::cmp::Ordering::*; + + if self.int_val.is_zero() { + return BigDecimal::new(BigInt::zero(), new_scale); + } + + match new_scale.cmp(&self.scale) { + Ordering::Equal => { + self.clone() + } + Ordering::Greater => { + // increase number of zeros + let scale_diff = new_scale - self.scale; + let int_val = &self.int_val * ten_to_the(scale_diff as u64); + BigDecimal::new(int_val, new_scale) + } + Ordering::Less => { + let (sign, mut digits) = self.int_val.to_radix_le(10); + + let digit_count = digits.len(); + let int_digit_count = digit_count as i64 - self.scale; + let rounded_int = match int_digit_count.cmp(&-new_scale) { + Equal => { + let (&last_digit, remaining) = digits.split_last().unwrap(); + let trailing_zeros = remaining.iter().all(Zero::is_zero); + let rounded_digit = mode.round_pair(sign, (0, last_digit), trailing_zeros); + BigInt::new(sign, vec![rounded_digit as u32]) + } + Less => { + debug_assert!(!digits.iter().all(Zero::is_zero)); + let rounded_digit = mode.round_pair(sign, (0, 0), false); + BigInt::new(sign, vec![rounded_digit as u32]) + } + Greater => { + // location of new rounding point + let scale_diff = (self.scale - new_scale) as usize; + + let low_digit = digits[scale_diff - 1]; + let high_digit = digits[scale_diff]; + let trailing_zeros = digits[0..scale_diff-1].iter().all(Zero::is_zero); + let rounded_digit = mode.round_pair(sign, (high_digit, low_digit), trailing_zeros); + + debug_assert!(rounded_digit <= 10); + + if rounded_digit < 10 { + digits[scale_diff] = rounded_digit; + } else { + digits[scale_diff] = 0; + let mut i = scale_diff + 1; + loop { + if i == digit_count { + digits.push(1); + break; + } + + if digits[i] < 9 { + digits[i] += 1; + break; + } + + digits[i] = 0; + i += 1; + } + } + + BigInt::from_radix_le(sign, &digits[scale_diff..], 10).unwrap() + } + }; + + BigDecimal::new(rounded_int, new_scale) + } + } + } + + #[inline(always)] fn take_and_scale(mut self, new_scale: i64) -> BigDecimal { if self.int_val.is_zero() { From b6a12fe6062b8ef5da9cf09aac9c3dd61ec44c1b Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 6 Jun 2023 00:28:36 -0400 Subject: [PATCH 089/100] Add tests for with_scale_round --- src/lib.rs | 10 +++ src/lib.tests.with_scale_round.rs | 113 ++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/lib.tests.with_scale_round.rs diff --git a/src/lib.rs b/src/lib.rs index 48cf190..a4bafcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3400,3 +3400,13 @@ mod bigdecimal_tests { assert_eq!(value, expected); } } + + +#[cfg(test)] +#[allow(non_snake_case)] +mod test_with_scale_round { + use super::*; + use paste::paste; + + include!("lib.tests.with_scale_round.rs"); +} diff --git a/src/lib.tests.with_scale_round.rs b/src/lib.tests.with_scale_round.rs new file mode 100644 index 0000000..394378c --- /dev/null +++ b/src/lib.tests.with_scale_round.rs @@ -0,0 +1,113 @@ +// Test BigDecimal::with_scale_round + +macro_rules! impl_test { + ( name=$($name:expr)*; $scale:literal : $mode:ident => $ex:literal ) => { + paste! { + #[test] + fn [< $($name)* _rounding_ $mode >]() { + let bigdecimal = test_input(); + let result = bigdecimal.with_scale_round($scale as i64, RoundingMode::$mode); + let expected = BigDecimal::from_str($ex).unwrap(); + assert_eq!(result, expected); + assert_eq!(result.int_val, expected.int_val); + assert_eq!(result.scale, $scale); + } + } + }; + ( -$scale:literal $( : $($modes:ident),+ => $ex:literal )+ ) => { + $( $( impl_test!(name=scale_neg_ $scale; -$scale : $modes => $ex); )* )* + }; + ( $scale:literal $( : $($modes:ident),+ => $ex:literal )+ ) => { + $( $( impl_test!(name=scale_ $scale; $scale : $modes => $ex); )* )* + }; +} + + +mod case_3009788271450eNeg9 { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from_str("3009.788271450").unwrap() + } + + impl_test!(10 : Up, Down => "3009.7882714500"); + impl_test!(9 : Up, Down => "3009.788271450"); + impl_test!(8 : Up, Down, HalfEven => "3009.78827145"); + + impl_test!(7 : Up, Ceiling, HalfUp => "3009.7882715" + : Down, Floor, HalfDown, HalfEven => "3009.7882714"); + + impl_test!(4 : Up, Ceiling, HalfUp, HalfDown, HalfEven => "3009.7883" + : Down, Floor => "3009.7882"); + + impl_test!(2 : Up => "3009.79" + : Down => "3009.78"); + + impl_test!(1 : Up => "3009.8" + : Down => "3009.7"); + + impl_test!(0 : Up => "3010" + : Down => "3009"); + + impl_test!( -1 : Up => "301e1"); + impl_test!( -2 : Up => "31e2"); + impl_test!( -3 : Up => "4e3"); + impl_test!( -4 : Up => "1e4" ); + impl_test!( -5 : Up => "1e5" : Down => "0"); + impl_test!( -20 : Up => "1e20" : Down => "0"); +} + +mod case_neg_636652287787259 { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from_str("-636652287787259").unwrap() + } + + impl_test!(1 : Up, Down => "-636652287787259.0"); + impl_test!(0 : Up, Down => "-636652287787259"); + impl_test!(-1 : Up => "-63665228778726e1" + : Down => "-63665228778725e1"); + impl_test!(-12 : Up => "-637e12" + : Down => "-636e12"); +} + +mod case_99999999999999999999999eNeg4 { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from_str("99999999999999999999999e-4").unwrap() + } + + impl_test!(4 : Up => "9999999999999999999.9999"); + impl_test!(3 : Up => "10000000000000000000.000" + : Down => "9999999999999999999.999"); + impl_test!(-3 : Up => "10000000000000000e3" + : Down => "9999999999999999e3"); +} + +mod case_369708962060657eNeg30 { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from_str("369708962060657E-30").unwrap() + } + + impl_test!(4 : Up => "1e-4"); + impl_test!(20 : Up => "36971e-20" + : Down => "36970e-20"); +} + +mod case_682829560896740e30 { + use super::*; + + fn test_input() -> BigDecimal { + BigDecimal::from_str("682829560896740e30").unwrap() + } + + impl_test!(4 : Up => "682829560896740000000000000000000000000000000.0000"); + impl_test!(0 : Up => "682829560896740000000000000000000000000000000"); + impl_test!(-35 : Up => "6828295609e35"); + impl_test!(-36 : Up => "682829561e36"); + impl_test!(-100 : Up => "1e100"); +} From 9f19fe8343dd12480d3b03f5aea84e0fbbe08350 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 3 Jul 2023 00:10:19 -0400 Subject: [PATCH 090/100] Add lint stage to gitlab and change base images --- .gitlab-ci.yml | 57 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 244a1d9..6e5c1ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,27 +17,55 @@ variables: CARGO_NET_GIT_FETCH_WITH_CLI: 'true' +stages: + - check + - build + - test + - deploy + + +.cargo-check-script: &cargo-check-script + script: + - rustc --version && cargo --version + - cargo check + .cargo:build-script: &cargo-build-script script: - rustc --version && cargo --version - cargo build - .cargo-test-script: &cargo-test-script script: - rustc --version && cargo --version - cargo test --verbose +cargo:check: + stage: check + image: akubera/rust:stable + script: + <<: *cargo-check-script + +cargo:clippy: + stage: check + image: "akubera/rust:stable" + needs: + - cargo:check + allow_failure: true + script: + - cargo clippy -- -Dclippy::{dbg_macro,todo} + + cargo:build-stable: stage: build - image: "rust:latest" + image: akubera/rust:stable + needs: + - cargo:check <<: *cargo-build-script - cargo:test-stable: stage: test - image: "rust:latest" + image: akubera/rust:stable needs: - "cargo:build-stable" <<: *cargo-build-script @@ -59,9 +87,16 @@ cargo:test-nightly: <<: *cargo-test-script +cargo:check-1.43: + stage: check + image: "akubera/rust-kcov:1.43.1-buster" + <<: *cargo-check-script + cargo:build-1.43: stage: build image: "akubera/rust-kcov:1.43.1-buster" + needs: + - "cargo:check-1.43" <<: *cargo-build-script cargo:test-1.43: @@ -72,9 +107,16 @@ cargo:test-1.43: <<: *cargo-test-script +cargo:check-1.54: + stage: check + image: "akubera/rust-kcov:1.54.0-bullseye" + <<: *cargo-check-script + cargo:build-1.54: stage: build image: "akubera/rust-kcov:1.54.0-bullseye" + needs: + - "cargo:check-1.54" <<: *cargo-build-script cargo:test-1.54: @@ -85,9 +127,16 @@ cargo:test-1.54: <<: *cargo-test-script +cargo:check-1.70: + stage: check + image: "akubera/rust-grcov:1.70.0-bullseye" + <<: *cargo-check-script + cargo:build-1.70: stage: build image: "akubera/rust-grcov:1.70.0-bullseye" + needs: + - "cargo:check-1.70" <<: *cargo-build-script cargo:test-1.70: From 34e8912c2ab3f4805cf72889cda42cf2d3345b3f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 3 Jul 2023 21:15:37 -0400 Subject: [PATCH 091/100] Add builds/ dir to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0126c8f..3be4a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ target .*cache/ benches/test-data/ + +builds/ From 9fb29b0250dc9ee8170017cdc48b5fd3259bf969 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 3 Jul 2023 23:01:32 -0400 Subject: [PATCH 092/100] Add clippy msrv and update allowed lints --- .clippy.toml | 1 + build.rs | 2 ++ src/lib.rs | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..802b94f --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +msrv = "1.43.0" diff --git a/build.rs b/build.rs index f01fd1d..8bc8be9 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +#![allow(clippy::style)] + use std::env; use std::fs::File; diff --git a/src/lib.rs b/src/lib.rs index a4bafcf..46a22f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,12 +40,13 @@ //! println!("Input ({}) with 10 decimals: {} vs {})", input, dec, float); //! ``` #![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::style)] #![allow(clippy::unreadable_literal)] #![allow(clippy::needless_return)] #![allow(clippy::suspicious_arithmetic_impl)] #![allow(clippy::suspicious_op_assign_impl)] #![allow(clippy::redundant_field_names)] -#![allow(clippy::match_like_matches_macro)] // requires Rust 1.42.0 + pub extern crate num_bigint; pub extern crate num_traits; From 7b616ed325993d4c837cb21ad577d1bf9c60489f Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 3 Jul 2023 23:08:24 -0400 Subject: [PATCH 093/100] Fix some clippy warnings --- src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 46a22f2..3d01bab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -844,7 +844,7 @@ impl BigDecimal { } let (sign, mut digits) = self.int_val.to_radix_be(10); let trailing_count = digits.iter().rev().take_while(|i| **i == 0).count(); - let trunc_to = digits.len() - trailing_count as usize; + let trunc_to = digits.len() - trailing_count; digits.truncate(trunc_to); let int_val = BigInt::from_radix_be(sign, &digits, 10).unwrap(); let scale = self.scale - trailing_count as i64; @@ -1163,7 +1163,7 @@ impl<'a> AddAssign<&'a BigDecimal> for BigDecimal { } } -impl<'a> AddAssign for BigDecimal { +impl AddAssign for BigDecimal { #[inline] fn add_assign(&mut self, rhs: BigInt) { *self += BigDecimal::new(rhs, 0) @@ -1330,7 +1330,7 @@ impl<'a> SubAssign<&'a BigDecimal> for BigDecimal { } } -impl<'a> SubAssign for BigDecimal { +impl SubAssign for BigDecimal { #[inline(always)] fn sub_assign(&mut self, rhs: BigInt) { *self -= BigDecimal::new(rhs, 0) @@ -1865,7 +1865,7 @@ impl fmt::Display for BigDecimal { // Case 2.1, entirely before the decimal point // We should prepend zeros let zeros = location as usize - abs_int.len(); - let abs_int = abs_int + "0".repeat(zeros as usize).as_str(); + let abs_int = abs_int + "0".repeat(zeros).as_str(); (abs_int, "".to_string()) } else { // Case 2.2, somewhere around the decimal point @@ -1895,10 +1895,7 @@ impl fmt::Display for BigDecimal { before }; - let non_negative = match self.int_val.sign() { - Sign::Plus | Sign::NoSign => true, - _ => false, - }; + let non_negative = matches!(self.int_val.sign(), Sign::Plus | Sign::NoSign); //pad_integral does the right thing although we have a decimal f.pad_integral(non_negative, "", &complete_without_sign) } From 686184cb8a5299338b44a7fed755d1b4d89a2c70 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Mon, 3 Jul 2023 23:10:54 -0400 Subject: [PATCH 094/100] Update copyright date --- LICENSE-APACHE | 2 +- LICENSE-MIT | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 563ae93..fe561ca 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2017 The BigDecimal-rs Contributors +Copyright 2023 The BigDecimal-rs Contributors Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/LICENSE-MIT b/LICENSE-MIT index 5f73012..103bad0 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2017 The BigDecimal-rs Contributors +Copyright (c) 2023 The BigDecimal-rs Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/src/lib.rs b/src/lib.rs index 3d01bab..572c6b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ // Copyright 2016 Adam Sunderland -// 2016-2017 Andrew Kubera +// 2016-2023 Andrew Kubera // 2017 Ruben De Smet // See the COPYRIGHT file at the top-level directory of this // distribution. From a61b74cc8d41ee9e123fb14ff6f4c79bcbb3e129 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 4 Jul 2023 14:29:39 -0400 Subject: [PATCH 095/100] Improve BigDecimal docstrings and doctests --- src/lib.rs | 140 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 113 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 572c6b1..54c5a2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,6 +243,16 @@ impl BigDecimal { } /// Return a new BigDecimal after shortening the digits and rounding + /// + /// ``` + /// # use bigdecimal::*; + /// + /// let n: BigDecimal = "129.41675".parse().unwrap(); + /// + /// assert_eq!(n.with_scale_round(2, RoundingMode::Up), "129.42".parse().unwrap()); + /// assert_eq!(n.with_scale_round(-1, RoundingMode::Down), "120".parse().unwrap()); + /// assert_eq!(n.with_scale_round(4, RoundingMode::HalfEven), "129.4168".parse().unwrap()); + /// ``` pub fn with_scale_round(&self, new_scale: i64, mode: RoundingMode) -> BigDecimal { use stdlib::cmp::Ordering::*; @@ -318,8 +328,11 @@ impl BigDecimal { } } - - #[inline(always)] + /// Return a new BigDecimal object with same value and given scale, + /// padding with zeros or truncating digits as needed + /// + /// Useful for aligning decimals before adding/subtracting. + /// fn take_and_scale(mut self, new_scale: i64) -> BigDecimal { if self.int_val.is_zero() { return BigDecimal::new(BigInt::zero(), new_scale); @@ -340,7 +353,19 @@ impl BigDecimal { /// Return a new BigDecimal object with precision set to new value /// - #[inline] + /// ``` + /// # use bigdecimal::*; + /// + /// let n: BigDecimal = "129.41675".parse().unwrap(); + /// + /// assert_eq!(n.with_prec(2), "130".parse().unwrap()); + /// + /// let n_p12 = n.with_prec(12); + /// let (i, scale) = n_p12.as_bigint_and_exponent(); + /// assert_eq!(n_p12, "129.416750000".parse().unwrap()); + /// assert_eq!(i, 129416750000_u64.into()); + /// assert_eq!(scale, 9); + /// ``` pub fn with_prec(&self, prec: u64) -> BigDecimal { let digits = self.digits(); @@ -373,16 +398,17 @@ impl BigDecimal { /// Return the sign of the `BigDecimal` as `num::bigint::Sign`. /// - /// # Examples - /// /// ``` - /// extern crate num_bigint; - /// extern crate bigdecimal; - /// use std::str::FromStr; + /// # use bigdecimal::{BigDecimal, num_bigint::Sign}; + /// + /// fn sign_of(src: &str) -> Sign { + /// let n: BigDecimal = src.parse().unwrap(); + /// n.sign() + /// } /// - /// assert_eq!(bigdecimal::BigDecimal::from_str("-1").unwrap().sign(), num_bigint::Sign::Minus); - /// assert_eq!(bigdecimal::BigDecimal::from_str("0").unwrap().sign(), num_bigint::Sign::NoSign); - /// assert_eq!(bigdecimal::BigDecimal::from_str("1").unwrap().sign(), num_bigint::Sign::Plus); + /// assert_eq!(sign_of("-1"), Sign::Minus); + /// assert_eq!(sign_of("0"), Sign::NoSign); + /// assert_eq!(sign_of("1"), Sign::Plus); /// ``` #[inline] pub fn sign(&self) -> num_bigint::Sign { @@ -395,12 +421,12 @@ impl BigDecimal { /// # Examples /// /// ``` - /// extern crate num_bigint; - /// extern crate bigdecimal; - /// use std::str::FromStr; + /// use bigdecimal::{BigDecimal, num_bigint::BigInt}; /// - /// assert_eq!(bigdecimal::BigDecimal::from_str("1.1").unwrap().as_bigint_and_exponent(), - /// (num_bigint::BigInt::from_str("11").unwrap(), 1)); + /// let n: BigDecimal = "1.23456".parse().unwrap(); + /// let expected = ("123456".parse::().unwrap(), 5); + /// assert_eq!(n.as_bigint_and_exponent(), expected); + /// ``` #[inline] pub fn as_bigint_and_exponent(&self) -> (BigInt, i64) { (self.int_val.clone(), self.scale) @@ -412,12 +438,12 @@ impl BigDecimal { /// # Examples /// /// ``` - /// extern crate num_bigint; - /// extern crate bigdecimal; - /// use std::str::FromStr; + /// use bigdecimal::{BigDecimal, num_bigint::BigInt}; /// - /// assert_eq!(bigdecimal::BigDecimal::from_str("1.1").unwrap().into_bigint_and_exponent(), - /// (num_bigint::BigInt::from_str("11").unwrap(), 1)); + /// let n: BigDecimal = "1.23456".parse().unwrap(); + /// let expected = ("123456".parse::().unwrap(), 5); + /// assert_eq!(n.into_bigint_and_exponent(), expected); + /// ``` #[inline] pub fn into_bigint_and_exponent(self) -> (BigInt, i64) { (self.int_val, self.scale) @@ -431,6 +457,15 @@ impl BigDecimal { } /// Compute the absolute value of number + /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "123.45".parse().unwrap(); + /// assert_eq!(n.abs(), "123.45".parse().unwrap()); + /// + /// let n: BigDecimal = "-123.45".parse().unwrap(); + /// assert_eq!(n.abs(), "123.45".parse().unwrap()); + /// ``` #[inline] pub fn abs(&self) -> BigDecimal { BigDecimal { @@ -439,7 +474,13 @@ impl BigDecimal { } } - #[inline] + /// Multiply decimal by 2 (efficiently) + /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "123.45".parse().unwrap(); + /// assert_eq!(n.double(), "246.90".parse().unwrap()); + /// ``` pub fn double(&self) -> BigDecimal { if self.is_zero() { self.clone() @@ -451,11 +492,16 @@ impl BigDecimal { } } - /// Divide this efficiently by 2 + /// Divide decimal by 2 (efficiently) /// - /// Note, if this is odd, the precision will increase by 1, regardless - /// of the context's limit. + /// *Note*: If the last digit in the decimal is odd, the precision + /// will increase by 1 /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "123.45".parse().unwrap(); + /// assert_eq!(n.half(), "61.725".parse().unwrap()); + /// ``` #[inline] pub fn half(&self) -> BigDecimal { if self.is_zero() { @@ -473,8 +519,22 @@ impl BigDecimal { } } + /// Square a decimal: *x²* /// - #[inline] + /// No rounding or truncating of digits; this is the full result + /// of the squaring operation. + /// + /// *Note*: doubles the scale of bigdecimal, which might lead to + /// accidental exponential-complexity if used in a loop. + /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "1.1156024145937225657484".parse().unwrap(); + /// assert_eq!(n.square(), "1.24456874744734405154288399835406316085210256".parse().unwrap()); + /// + /// let n: BigDecimal = "-9.238597585E+84".parse().unwrap(); + /// assert_eq!(n.square(), "8.5351685337567832225E+169".parse().unwrap()); + /// ``` pub fn square(&self) -> BigDecimal { if self.is_zero() || self.is_one() { self.clone() @@ -486,7 +546,22 @@ impl BigDecimal { } } - #[inline] + /// Cube a decimal: *x³* + /// + /// No rounding or truncating of digits; this is the full result + /// of the cubing operation. + /// + /// *Note*: triples the scale of bigdecimal, which might lead to + /// accidental exponential-complexity if used in a loop. + /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "1.1156024145937225657484".parse().unwrap(); + /// assert_eq!(n.cube(), "1.388443899780141911774491376394890472130000455312878627147979955904".parse().unwrap()); + /// + /// let n: BigDecimal = "-9.238597585E+84".parse().unwrap(); + /// assert_eq!(n.cube(), "-7.88529874035334084567570176625E+254".parse().unwrap()); + /// ``` pub fn cube(&self) -> BigDecimal { if self.is_zero() || self.is_one() { self.clone() @@ -500,8 +575,19 @@ impl BigDecimal { /// Take the square root of the number /// + /// Uses default-precision, set from build time environment variable + //// `RUST_BIGDECIMAL_DEFAULT_PRECISION` (defaults to 100) + /// /// If the value is < 0, None is returned /// + /// ``` + /// # use bigdecimal::BigDecimal; + /// let n: BigDecimal = "1.1156024145937225657484".parse().unwrap(); + /// assert_eq!(n.sqrt().unwrap(), "1.056220817156016181190291268045893004363809142172289919023269377496528394924695970851558013658193913".parse().unwrap()); + /// + /// let n: BigDecimal = "-9.238597585E+84".parse().unwrap(); + /// assert_eq!(n.sqrt(), None); + /// ``` #[inline] pub fn sqrt(&self) -> Option { if self.is_zero() || self.is_one() { From 024b4dad9963c60dc529759e8d831314dff73e04 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 4 Jul 2023 14:32:21 -0400 Subject: [PATCH 096/100] Add test:no-std and cargo-semver jobs to gitlab-ci --- .gitlab-ci.yml | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e5c1ef..714c0f6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,12 @@ variables: # this fixes an error when updating cargo registry CARGO_NET_GIT_FETCH_WITH_CLI: 'true' + # default arguments to pass to cargo test + CARGO_BUILD_ARGS: "" + + # default arguments to pass to cargo test + CARGO_TEST_ARGS: "--verbose" + stages: - check @@ -29,21 +35,20 @@ stages: - rustc --version && cargo --version - cargo check -.cargo:build-script: &cargo-build-script +.cargo-build-script: &cargo-build-script script: - rustc --version && cargo --version - - cargo build + - cargo build $CARGO_BUILD_ARGS .cargo-test-script: &cargo-test-script script: - rustc --version && cargo --version - - cargo test --verbose + - cargo test $CARGO_TEST_ARGS cargo:check: stage: check image: akubera/rust:stable - script: <<: *cargo-check-script cargo:clippy: @@ -55,6 +60,15 @@ cargo:clippy: script: - cargo clippy -- -Dclippy::{dbg_macro,todo} +cargo:semver-checks: + stage: check + image: "akubera/rust:stable" + needs: + - cargo:check + allow_failure: true + script: + - cargo semver-checks + cargo:build-stable: stage: build @@ -68,8 +82,26 @@ cargo:test-stable: image: akubera/rust:stable needs: - "cargo:build-stable" + <<: *cargo-test-script + +cargo:build:no-std: + stage: build + image: akubera/rust:stable + needs: + - cargo:check + variables: + CARGO_BUILD_ARGS: "--no-default-features --lib" <<: *cargo-build-script +cargo:test:no-std: + stage: test + image: akubera/rust:stable + needs: + - "cargo:build:no-std" + variables: + CARGO_TEST_ARGS: "--no-default-features --lib" + <<: *cargo-test-script + cargo:build-nightly: stage: build From bb31fda0375d3da31274735a80f537b7296517ef Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Tue, 4 Jul 2023 15:16:47 -0400 Subject: [PATCH 097/100] Move tests of double to separate test file --- src/lib.rs | 23 ++++++----------------- src/lib.tests.double.rs | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 src/lib.tests.double.rs diff --git a/src/lib.rs b/src/lib.rs index 54c5a2a..ff06a97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2396,10 +2396,12 @@ mod bigdecimal_serde { #[rustfmt::skip] #[cfg(test)] +#[allow(non_snake_case)] mod bigdecimal_tests { use crate::{stdlib, BigDecimal, ToString, FromStr, TryFrom}; use num_traits::{ToPrimitive, FromPrimitive, Signed, Zero, One}; use num_bigint; + use paste::paste; #[test] fn test_sum() { @@ -3234,23 +3236,10 @@ mod bigdecimal_tests { } } - #[test] - fn test_double() { - let vals = vec![ - ("1", "2"), - ("1.00", "2.00"), - ("1.50", "3.00"), - ("5", "10"), - ("5.0", "10.0"), - ("5.5", "11.0"), - ("5.05", "10.10"), - ]; - for &(x, y) in vals.iter() { - let a = BigDecimal::from_str(x).unwrap().double(); - let b = BigDecimal::from_str(y).unwrap(); - assert_eq!(a, b); - assert_eq!(a.scale, b.scale); - } + mod double { + use super::*; + + include!("lib.tests.double.rs"); } #[test] diff --git a/src/lib.tests.double.rs b/src/lib.tests.double.rs new file mode 100644 index 0000000..0f71390 --- /dev/null +++ b/src/lib.tests.double.rs @@ -0,0 +1,25 @@ +// Test BigDecimal::double + +macro_rules! impl_case { + ($name:ident : $a:literal => $ex:literal ) => { + paste! { + #[test] + fn $name() { + let value = BigDecimal::from_str($a).unwrap(); + let expected = BigDecimal::from_str($ex).unwrap(); + let result = value.double(); + assert_eq!(result, expected); + assert_eq!(result.int_val, expected.int_val); + assert_eq!(result.scale, expected.scale); + } + } + }; +} + +impl_case!(case_zero : "0" => "0"); +impl_case!(case_1 : "1" => "2"); +impl_case!(case_100Em2 : "1.00" => "2.00"); +impl_case!(case_150Em2 : "1.50" => "3.00"); +impl_case!(case_neg150Em2 : "-1.50" => "-3.00"); +impl_case!(case_32909E4 : "32909E4" => "6.5818E+8"); +impl_case!(case_1_1156024145937225657484 : "1.1156024145937225657484" => "2.2312048291874451314968"); From 695332cb08c5ac79572a207da6c9dad1f51c4cf2 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 5 Jul 2023 00:10:26 -0400 Subject: [PATCH 098/100] Split cache policies in gitlab-ci --- .gitlab-ci.yml | 70 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 714c0f6..9e6e081 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,13 +1,17 @@ cache: - policy: pull-push - # should we only share cache between jobs on one tag? - # key: ${CI_COMMIT_REF_SLUG} - paths: - - .cargo/registry/index - - .cargo/registry/cache - - target/debug/deps - - target/debug/build + - key: cargo-registry + policy: pull + paths: + - .cargo/registry/index + - .cargo/registry/cache + - target/debug/deps + # each job manually specifies its build cache - no way to automate on version + - key: rustbuild-${RUST_CACHE_KEY} + policy: pull-push + paths: + - target/debug/deps + - target/debug/build variables: # store cargo registry in the project directory @@ -38,17 +42,33 @@ stages: .cargo-build-script: &cargo-build-script script: - rustc --version && cargo --version + - printenv CARGO_BUILD_ARGS - cargo build $CARGO_BUILD_ARGS .cargo-test-script: &cargo-test-script script: - rustc --version && cargo --version + - printenv CARGO_TEST_ARGS - cargo test $CARGO_TEST_ARGS cargo:check: stage: check image: akubera/rust:stable + cache: + - key: cargo-registry + policy: pull-push + paths: + - .cargo/registry/index + - .cargo/registry/cache + - target/debug/deps + - key: rustbuild-${RUST_CACHE_KEY} + policy: pull-push + paths: + - target/debug/deps + - target/debug/build + variables: + RUST_CACHE_KEY: "stable" <<: *cargo-check-script cargo:clippy: @@ -57,6 +77,8 @@ cargo:clippy: needs: - cargo:check allow_failure: true + variables: + RUST_CACHE_KEY: "stable" script: - cargo clippy -- -Dclippy::{dbg_macro,todo} @@ -66,6 +88,8 @@ cargo:semver-checks: needs: - cargo:check allow_failure: true + variables: + RUST_CACHE_KEY: "stable" script: - cargo semver-checks @@ -75,6 +99,8 @@ cargo:build-stable: image: akubera/rust:stable needs: - cargo:check + variables: + RUST_CACHE_KEY: "stable" <<: *cargo-build-script cargo:test-stable: @@ -82,6 +108,8 @@ cargo:test-stable: image: akubera/rust:stable needs: - "cargo:build-stable" + variables: + RUST_CACHE_KEY: "stable" <<: *cargo-test-script cargo:build:no-std: @@ -90,6 +118,7 @@ cargo:build:no-std: needs: - cargo:check variables: + RUST_CACHE_KEY: "stable+no_std" CARGO_BUILD_ARGS: "--no-default-features --lib" <<: *cargo-build-script @@ -99,6 +128,7 @@ cargo:test:no-std: needs: - "cargo:build:no-std" variables: + RUST_CACHE_KEY: "stable+no_std" CARGO_TEST_ARGS: "--no-default-features --lib" <<: *cargo-test-script @@ -107,6 +137,8 @@ cargo:build-nightly: stage: build image: rustlang/rust:nightly allow_failure: true + variables: + RUST_CACHE_KEY: "nightly" <<: *cargo-build-script @@ -116,12 +148,16 @@ cargo:test-nightly: needs: - cargo:build-nightly allow_failure: true + variables: + RUST_CACHE_KEY: "nightly" <<: *cargo-test-script cargo:check-1.43: stage: check image: "akubera/rust-kcov:1.43.1-buster" + variables: + RUST_CACHE_KEY: "1.43" <<: *cargo-check-script cargo:build-1.43: @@ -129,6 +165,8 @@ cargo:build-1.43: image: "akubera/rust-kcov:1.43.1-buster" needs: - "cargo:check-1.43" + variables: + RUST_CACHE_KEY: "1.43" <<: *cargo-build-script cargo:test-1.43: @@ -136,12 +174,16 @@ cargo:test-1.43: needs: - "cargo:build-1.43" image: "akubera/rust-kcov:1.43.1-buster" + variables: + RUST_CACHE_KEY: "1.43" <<: *cargo-test-script cargo:check-1.54: stage: check image: "akubera/rust-kcov:1.54.0-bullseye" + variables: + RUST_CACHE_KEY: "1.54" <<: *cargo-check-script cargo:build-1.54: @@ -149,6 +191,8 @@ cargo:build-1.54: image: "akubera/rust-kcov:1.54.0-bullseye" needs: - "cargo:check-1.54" + variables: + RUST_CACHE_KEY: "1.54" <<: *cargo-build-script cargo:test-1.54: @@ -156,12 +200,16 @@ cargo:test-1.54: needs: - "cargo:build-1.54" image: "akubera/rust-kcov:1.54.0-bullseye" + variables: + RUST_CACHE_KEY: "1.54" <<: *cargo-test-script cargo:check-1.70: stage: check image: "akubera/rust-grcov:1.70.0-bullseye" + variables: + RUST_CACHE_KEY: "1.70" <<: *cargo-check-script cargo:build-1.70: @@ -169,6 +217,8 @@ cargo:build-1.70: image: "akubera/rust-grcov:1.70.0-bullseye" needs: - "cargo:check-1.70" + variables: + RUST_CACHE_KEY: "1.70" <<: *cargo-build-script cargo:test-1.70: @@ -176,6 +226,8 @@ cargo:test-1.70: needs: - "cargo:build-1.70" image: "akubera/rust-grcov:1.70.0-bullseye" + variables: + RUST_CACHE_KEY: "1.70" <<: *cargo-test-script @@ -186,6 +238,7 @@ coverage-test: image: "akubera/rust-grcov:1.70.0-bullseye" variables: + RUST_CACHE_KEY: "1.70" CARGO_NET_GIT_FETCH_WITH_CLI: 'true' LLVM_PROFILE_FILE: "target/coverage/%p-%m.profraw" RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off " @@ -219,6 +272,7 @@ cargo:benchmark: allow_failure: true cache: [] variables: + RUST_CACHE_KEY: "1.70" CARGO_HOME: /usr/local/cargo BENCHMARK_EXTRAS: "1" script: From 60ac44071c92c383a31ceeb464f9aa0944c4ce46 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 5 Jul 2023 02:12:22 -0400 Subject: [PATCH 099/100] Remove extra keywords from Cargo.toml metadata --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1378c74..7d27d76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,14 +7,12 @@ documentation = "https://docs.rs/bigdecimal" homepage = "https://github.com/akubera/bigdecimal-rs" repository = "https://github.com/akubera/bigdecimal-rs" keywords = [ - "mathematics", "numerics", "bignum", "decimal", "arbitrary-precision", - "floating-point", - "no-std", ] +categories = [ "mathematics", "science", "no-std" ] license = "MIT/Apache-2.0" autobenches = false From 25dab381f5f52e8fff9411aa53a6b96fd5e52a20 Mon Sep 17 00:00:00 2001 From: Andrew Kubera Date: Wed, 5 Jul 2023 01:03:02 -0400 Subject: [PATCH 100/100] Version 0.4.0 --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d27d76..8ae8805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bigdecimal" -version = "0.4.0+dev" +version = "0.4.0" authors = ["Andrew Kubera"] description = "Arbitrary precision decimal numbers" documentation = "https://docs.rs/bigdecimal" diff --git a/README.md b/README.md index 0dc3274..2644046 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Add bigdecimal as a dependency to your `Cargo.toml` file: ```toml [dependencies] -bigdecimal = "0.3" +bigdecimal = "0.4" ``` Import and use the `BigDecimal` struct to solve your problems: