Skip to content

Commit ff6f01b

Browse files
committed
Use canonical representation of numbers.
10^2 should have a clean internal representation of 1 with exp. 2 and no offset.
1 parent 147826b commit ff6f01b

File tree

1 file changed

+57
-18
lines changed

1 file changed

+57
-18
lines changed

src/stack.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::state::State;
77

88
pub struct Stack {
99
s: VecDeque<BigDecimal>,
10+
// Precision when taking a snapshot (not of internal representation).
1011
precision: u64,
1112
}
1213

@@ -60,20 +61,20 @@ impl Stack {
6061
self.s.push_front(v);
6162
}
6263
Op::Add => {
63-
let [b, a] = self.pop()?;
64+
let [a, b] = self.pop()?;
6465
self.s.push_front(a + b);
6566
}
6667
Op::Subtract => {
67-
let [b, a] = self.pop()?;
68+
let [a, b] = self.pop()?;
6869
self.s.push_front(a - b);
6970
}
7071
Op::Multiply => {
71-
let [b, a] = self.pop()?;
72+
let [a, b] = self.pop()?;
7273
self.s.push_front(a * b);
7374
}
7475
Op::Divide => {
75-
let [b, a] = self.check_and_pop(|stack: &[BigDecimal; 2]| {
76-
if stack[0] == BigDecimal::zero() {
76+
let [a, b] = self.check_and_pop(|stack: &[BigDecimal; 2]| {
77+
if stack[1] == BigDecimal::zero() {
7778
Err(StackError::InvalidArgument(
7879
"element 1 must be non-zero".into(),
7980
))
@@ -84,7 +85,7 @@ impl Stack {
8485
self.s.push_front(a / b);
8586
}
8687
Op::Modulo => {
87-
let [b, a] = self.pop()?;
88+
let [a, b] = self.pop()?;
8889
self.s.push_front(a % b);
8990
}
9091
Op::Sqrt => {
@@ -100,36 +101,40 @@ impl Stack {
100101
self.s.push_front(a.sqrt().unwrap());
101102
}
102103
Op::Pow => {
103-
let [b, a] = self.check_and_pop(|stack: &[BigDecimal; 2]| {
104-
if !(stack[0].is_integer() && stack[0] > BigDecimal::zero()) {
104+
// This is the only operation that needs to crack open the representation.
105+
// Careful, BigDecimal's scale works not only as the number of digits after
106+
// the dot, it's really a generalized
107+
// int_value . 10^-scale
108+
let [a, b] = self.prep_and_pop(|stack: &[BigDecimal; 2]| {
109+
let [a, b] = stack;
110+
if !(b.is_integer() && b > &BigDecimal::zero() && b < &u64::MAX.into()) {
105111
return Err(StackError::InvalidArgument(
106112
"element 1 must be a positive integer".into(),
107113
));
108114
}
109-
if !stack[1].is_integer() {
115+
if !a.is_integer() {
110116
return Err(StackError::InvalidArgument(
111117
"element 2 must be an integer".into(),
112118
));
113119
}
114-
let a = stack[1].as_bigint_and_scale().0.into_owned();
115-
let b = stack[0].as_bigint_and_scale().0.into_owned();
120+
// We know the numbers are integers, but we still need to flush all
121+
// the digits into the bigint where we can express the Pow operation.
122+
let a = a.with_scale(0).as_bigint_and_scale().0.into_owned();
123+
let b = b.with_scale(0).as_bigint_and_scale().0.into_owned();
116124
// Arbitrarily cap the number of digits of the result to avoid
117125
// accidental freeze / memory blowup when pressing ^ too many times.
118126
if BigInt::from(a.bits()) * &b > BigInt::from(MAX_BIT_COUNT) {
119127
return Err(StackError::InvalidArgument(
120128
"chickening out of creating such a large result".into(),
121129
));
122130
}
123-
Ok(())
131+
Ok([a, b])
124132
})?;
125-
let a = a.as_bigint_and_scale().0.into_owned();
126-
let b = b.as_bigint_and_scale().0.into_owned();
127133
let result = a.pow(b.to_biguint().unwrap());
128134
// Normalization ensures the exponent representation is simplified.
129135
// For instance 10^100 -> (1, -100) after normalization instead of
130136
// (1e100, 0).
131-
self.s
132-
.push_front(BigDecimal::from_bigint(result, 0).normalized());
137+
self.s.push_front(BigDecimal::from_bigint(result, 0));
133138
}
134139
Op::Duplicate => {
135140
let [a] = self.pop()?;
@@ -155,7 +160,7 @@ impl Stack {
155160
self.precision = a.to_u64().unwrap();
156161
}
157162
Op::Rotate => {
158-
let [b, a] = self.pop()?;
163+
let [a, b] = self.pop()?;
159164
self.s.push_front(b);
160165
self.s.push_front(a);
161166
}
@@ -179,25 +184,43 @@ impl Stack {
179184
.collect()
180185
}
181186

187+
// Validate a segment of the stack through a user-provided function and return it.
188+
// Note: the elements are returned in the reverse order of the stack, which is the
189+
// natural order for running operations.
182190
fn check_and_pop<const C: usize, F: Fn(&[BigDecimal; C]) -> Result<(), StackError>>(
183191
&mut self,
184192
validator: F,
185193
) -> Result<[BigDecimal; C], StackError> {
194+
self.prep_and_pop(move |input| {
195+
validator(input)?;
196+
Ok(input.clone())
197+
})
198+
}
199+
200+
// Transform a segment of the stack through a user-provided function and return it.
201+
// Note: the elements are returned in the reverse order of the stack, which is the
202+
// natural order for running operations.
203+
fn prep_and_pop<const C: usize, T, F: Fn(&[BigDecimal; C]) -> Result<[T; C], StackError>>(
204+
&mut self,
205+
validator: F,
206+
) -> Result<[T; C], StackError> {
186207
if self.s.len() < C {
187208
return Err(StackError::MissingValue(C));
188209
}
189210
let result = self
190211
.s
191212
.range(0..C)
213+
.rev()
192214
.cloned()
193215
.collect::<Vec<BigDecimal>>()
194216
.try_into()
195217
.unwrap();
196-
validator(&result)?;
218+
let result = validator(&result)?;
197219
self.s.drain(0..C);
198220
Ok(result)
199221
}
200222

223+
// Return a segment of the stack in reverse order.
201224
fn pop<const C: usize>(&mut self) -> Result<[BigDecimal; C], StackError> {
202225
self.check_and_pop(|_| Ok(()))
203226
}
@@ -217,6 +240,8 @@ impl TryFrom<State> for Stack {
217240

218241
#[cfg(test)]
219242
mod tests {
243+
use bigdecimal::num_bigint::{self};
244+
220245
use super::*;
221246

222247
#[test]
@@ -370,4 +395,18 @@ mod tests {
370395

371396
Ok(())
372397
}
398+
399+
#[test]
400+
fn pow_representation() -> Result<(), StackError> {
401+
let mut s = Stack::new();
402+
s.apply(Op::Push(10.into()))?;
403+
s.apply(Op::Push(2.into()))?;
404+
s.apply(Op::Pow)?;
405+
let r = s.snapshot()[0].clone();
406+
let (bi, s) = r.as_bigint_and_scale();
407+
408+
assert_eq!(*bi, BigInt::new(num_bigint::Sign::Plus, vec![100]));
409+
assert_eq!(s, 0);
410+
Ok(())
411+
}
373412
}

0 commit comments

Comments
 (0)