Skip to content

Commit 78a11ef

Browse files
committed
Add math Context struct
1 parent 020ded4 commit 78a11ef

File tree

2 files changed

+193
-3
lines changed

2 files changed

+193
-3
lines changed

src/context.rs

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

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)