Skip to content

Commit e4eca0b

Browse files
committed
Merge math-context feature into trunk
2 parents 020ded4 + 5e0f293 commit e4eca0b

File tree

3 files changed

+210
-6
lines changed

3 files changed

+210
-6
lines changed

build.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
#![allow(clippy::style)]
21

32
use std::env;
4-
use std::path::PathBuf;
3+
use std::path::{Path, PathBuf};
4+
55

66
fn main() {
77
let ac = autocfg::new();
@@ -15,11 +15,12 @@ fn main() {
1515

1616
let outdir: PathBuf = std::env::var_os("OUT_DIR").unwrap().into();
1717
write_default_precision_file(&outdir);
18+
write_default_rounding_mode(&outdir);
1819
}
1920

2021

2122
/// Create default_precision.rs, containg definition of constant DEFAULT_PRECISION loaded in src/lib.rs
22-
fn write_default_precision_file(outdir: &PathBuf) {
23+
fn write_default_precision_file(outdir: &Path) {
2324
let env_var = env::var("RUST_BIGDECIMAL_DEFAULT_PRECISION").unwrap_or_else(|_| "100".to_owned());
2425
println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_PRECISION");
2526

@@ -34,3 +35,14 @@ fn write_default_precision_file(outdir: &PathBuf) {
3435

3536
std::fs::write(rust_file_path, rust_file_contents).unwrap();
3637
}
38+
39+
/// Create default_rounding_mode.rs, using value of RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE environment variable
40+
fn write_default_rounding_mode(outdir: &Path) {
41+
let rounding_mode_name = env::var("RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE").unwrap_or_else(|_| "HalfEven".to_owned());
42+
println!("cargo:rerun-if-env-changed=RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE");
43+
44+
let rust_file_path = outdir.join("default_rounding_mode.rs");
45+
let rust_file_contents = format!("const DEFAULT_ROUNDING_MODE: RoundingMode = RoundingMode::{};", rounding_mode_name);
46+
47+
std::fs::write(rust_file_path, rust_file_contents).unwrap();
48+
}

src/context.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//! Define arithmetical context
2+
//!
3+
4+
use crate::rounding::RoundingMode;
5+
use stdlib::num::NonZeroU64;
6+
use stdlib;
7+
8+
use num_traits::{ToPrimitive, Zero};
9+
10+
use crate::{
11+
Sign,
12+
BigDecimal,
13+
BigDecimalRef
14+
};
15+
16+
// const DEFAULT_PRECISION: u64 = ${RUST_BIGDECIMAL_DEFAULT_PRECISION} or 100;
17+
include!(concat!(env!("OUT_DIR"), "/default_precision.rs"));
18+
// const DEFAULT_ROUNDING_MODE: RoundingMode = ${RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE} or HalfUp;
19+
include!(concat!(env!("OUT_DIR"), "/default_rounding_mode.rs"));
20+
21+
/// Mathematical Context
22+
///
23+
/// Stores rules for numerical operations, such as how to round and
24+
/// number of digits to keep.
25+
///
26+
/// Defaults are defined at compile time, determined by environment
27+
/// variables:
28+
///
29+
/// | Variable | Descripiton | default |
30+
/// |-----------------------------------------|-----------------|----------|
31+
/// | `RUST_BIGDECIMAL_DEFAULT_PRECISION` | digit precision | 100 |
32+
/// | `RUST_BIGDECIMAL_DEFAULT_ROUNDING_MODE` | rounding-mode | HalfEven |
33+
///
34+
/// It is recommended that the user set explict values of a Context and *not*
35+
/// rely on compile time constants, but the option is there if necessary.
36+
///
37+
#[derive(Debug, Clone)]
38+
pub struct Context {
39+
/// total number of digits
40+
precision: NonZeroU64,
41+
/// how to round
42+
rounding: RoundingMode,
43+
}
44+
45+
impl Context {
46+
/// Create context with precision and rounding mode
47+
pub fn new(precision: NonZeroU64, rounding: RoundingMode) -> Self {
48+
Context {
49+
precision: precision,
50+
rounding: rounding,
51+
}
52+
}
53+
54+
/// Copy context with new precision value
55+
pub fn with_precision(&self, precision: NonZeroU64) -> Self {
56+
Self {
57+
precision: precision,
58+
..*self
59+
}
60+
}
61+
62+
/// Copy context with new precision value
63+
pub fn with_prec<T: ToPrimitive>(&self, precision: T) -> Option<Self> {
64+
precision
65+
.to_u64()
66+
.map(NonZeroU64::new)
67+
.flatten()
68+
.map(|prec| {
69+
Self {
70+
precision: prec,
71+
..*self
72+
}
73+
})
74+
}
75+
76+
/// Copy context with new rounding mode
77+
pub fn with_rounding_mode(&self, mode: RoundingMode) -> Self {
78+
Self {
79+
rounding: mode,
80+
..*self
81+
}
82+
}
83+
84+
/// Return maximum precision
85+
pub fn precision(&self) -> NonZeroU64 {
86+
self.precision
87+
}
88+
89+
/// Return rounding mode
90+
pub fn rounding_mode(&self) -> RoundingMode {
91+
self.rounding
92+
}
93+
94+
/// Round digits x and y with the rounding mode
95+
pub(crate) fn round_pair(&self, sign: Sign, x: u8, y: u8, trailing_zeros: bool) -> u8 {
96+
self.rounding.round_pair(sign, (x, y), trailing_zeros)
97+
}
98+
99+
/// Round digits x and y with the rounding mode
100+
#[allow(dead_code)]
101+
pub(crate) fn round_pair_with_carry(&self, sign: Sign, x: u8, y: u8, trailing_zeros: bool, carry: &mut u8) -> u8 {
102+
let r = self.round_pair(sign, x, y, trailing_zeros);
103+
if r == 10 {
104+
*carry = 1;
105+
0
106+
} else {
107+
r
108+
}
109+
}
110+
}
111+
112+
impl stdlib::default::Default for Context {
113+
fn default() -> Self {
114+
Self {
115+
precision: NonZeroU64::new(DEFAULT_PRECISION).unwrap(),
116+
rounding: DEFAULT_ROUNDING_MODE,
117+
}
118+
}
119+
}
120+
121+
impl Context {
122+
/// Add two big digit references
123+
pub fn add_refs<'a, 'b, A, B>(&self, a: A, b: B) -> BigDecimal
124+
where
125+
A: Into<BigDecimalRef<'a>>,
126+
B: Into<BigDecimalRef<'b>>,
127+
{
128+
let mut sum = BigDecimal::zero();
129+
self.add_refs_into(a, b, &mut sum);
130+
sum
131+
}
132+
133+
/// Add two decimal refs, storing value in dest
134+
pub fn add_refs_into<'a, 'b, A, B>(&self, a: A, b: B, dest: &mut BigDecimal)
135+
where
136+
A: Into<BigDecimalRef<'a>>,
137+
B: Into<BigDecimalRef<'b>>,
138+
{
139+
let a = a.into();
140+
let b = b.into();
141+
let sum = a.to_owned() + b.to_owned();
142+
*dest = sum.with_precision_round(self.precision, self.rounding)
143+
}
144+
}
145+
146+
147+
#[cfg(test)]
148+
mod test_context {
149+
use super::*;
150+
151+
#[test]
152+
fn contstructor_and_setters() {
153+
let ctx = Context::default();
154+
let c = ctx.with_prec(44).unwrap();
155+
assert_eq!(c.precision.get(), 44);
156+
assert_eq!(c.rounding, RoundingMode::HalfEven);
157+
158+
let c = c.with_rounding_mode(RoundingMode::Down);
159+
assert_eq!(c.precision.get(), 44);
160+
assert_eq!(c.rounding, RoundingMode::Down);
161+
}
162+
163+
#[test]
164+
fn sum_two_references() {
165+
use stdlib::ops::Neg;
166+
167+
let ctx = Context::default();
168+
let a: BigDecimal = "209682.134972197168613072130300".parse().unwrap();
169+
let b: BigDecimal = "3.0782968222271332463325639E-12".parse().unwrap();
170+
171+
let sum = ctx.add_refs(&a, &b);
172+
assert_eq!(sum, "209682.1349721971716913689525271332463325639".parse().unwrap());
173+
174+
// make negative copy of b without cloning values
175+
let neg_b = b.to_ref().neg();
176+
177+
let sum = ctx.add_refs(&a, neg_b);
178+
assert_eq!(sum, "209682.1349721971655347753080728667536674361".parse().unwrap());
179+
180+
let sum = ctx.with_prec(27).unwrap().with_rounding_mode(RoundingMode::Up).add_refs(&a, neg_b);
181+
assert_eq!(sum, "209682.134972197165534775309".parse().unwrap());
182+
}
183+
}

src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,22 @@ mod parsing;
104104
pub mod rounding;
105105
pub use rounding::RoundingMode;
106106

107+
// Mathematical context
108+
mod context;
109+
pub use context::Context;
110+
107111
/// Return 10^pow
108112
///
109113
/// Try to calculate this with fewest number of allocations
110114
///
111115
fn ten_to_the(pow: u64) -> BigInt {
116+
ten_to_the_uint(pow).into()
117+
}
118+
119+
/// Return 10^pow
120+
fn ten_to_the_uint(pow: u64) -> BigUint {
112121
if pow < 20 {
113-
return BigInt::from(10u64.pow(pow as u32));
122+
return BigUint::from(10u64.pow(pow as u32));
114123
}
115124

116125
// linear case of 10^pow = 10^(19 * count + rem)
@@ -120,7 +129,7 @@ fn ten_to_the(pow: u64) -> BigInt {
120129
// count factors of 19
121130
let (count, rem) = pow.div_rem(&19);
122131

123-
let mut res = BigInt::from(ten_to_nineteen);
132+
let mut res = BigUint::from(ten_to_nineteen);
124133
for _ in 1..count {
125134
res *= ten_to_nineteen;
126135
}
@@ -133,7 +142,7 @@ fn ten_to_the(pow: u64) -> BigInt {
133142

134143
// use recursive algorithm where linear case might be too slow
135144
let (quotient, rem) = pow.div_rem(&16);
136-
let x = ten_to_the(quotient);
145+
let x = ten_to_the_uint(quotient);
137146

138147
let x2 = &x * &x;
139148
let x4 = &x2 * &x2;

0 commit comments

Comments
 (0)