Refactor a bunch of stuff into number.rs

This commit is contained in:
Tiffany Bennett 2016-08-11 19:42:11 -04:00
parent 4f90171276
commit ebe9efb7f7
2 changed files with 213 additions and 153 deletions

View file

@ -1,6 +1,5 @@
use std::collections::HashMap;
use std::collections::BTreeMap;
use gmp::mpz::Mpz;
use gmp::mpq::Mpq;
use chrono::{DateTime, FixedOffset};
use number::{Number, Unit};
@ -16,81 +15,32 @@ pub enum Value {
/// The evaluation context that contains unit definitions.
pub struct Context {
dimensions: Vec<String>,
units: HashMap<String, Number>,
aliases: HashMap<Unit, String>,
prefixes: Vec<(String, Number)>,
datepatterns: Vec<Vec<DatePattern>>,
pub dimensions: Vec<String>,
pub units: HashMap<String, Number>,
pub aliases: HashMap<Unit, String>,
pub prefixes: Vec<(String, Number)>,
pub datepatterns: Vec<Vec<DatePattern>>,
pub short_output: bool,
}
impl Context {
/// Provides a string representation of a Number. We can't impl
/// Display because it requires access to the Context for the unit
/// names.
pub fn show(&self, value: &Number) -> String {
use std::io::Write;
pub trait Show {
/// Provides a string representation of something, using information contained in a Context.
fn show(&self, context: &Context) -> String;
}
let mut out = vec![];
let mut frac = vec![];
let mut value = value.clone();
value.0.canonicalize();
let (exact, approx) = match number::to_string(&value.0) {
(true, v) => (v, None),
(false, v) => if value.0.get_den() > Mpz::from(1_000_000) || value.0.get_num() > Mpz::from(1_000_000_000u64) {
(format!("approx. {}", v), None)
} else {
(format!("{:?}", value.0), Some(v))
}
};
write!(out, "{}", exact).unwrap();
if let Some(approx) = approx {
write!(out, ", approx. {}", approx).unwrap();
impl Show for Value {
fn show(&self, context: &Context) -> String {
match *self {
Value::Number(ref num) => num.show(context),
Value::DateTime(ref dt) => format!("{}", dt)
}
for (&dim, &exp) in &value.1 {
if exp < 0 {
frac.push((dim, exp));
} else {
write!(out, " {}", self.dimensions[dim]).unwrap();
if exp != 1 {
write!(out, "^{}", exp).unwrap();
}
}
}
if frac.len() > 0 {
write!(out, " /").unwrap();
for (dim, exp) in frac {
let exp = -exp;
write!(out, " {}", self.dimensions[dim]).unwrap();
if exp != 1 {
write!(out, "^{}", exp).unwrap();
}
}
}
let alias = self.aliases.get(&value.1).cloned().or_else(|| {
if value.1.len() == 1 {
let e = value.1.iter().next().unwrap();
let ref n = self.dimensions[*e.0];
if *e.1 == 1 {
Some(n.clone())
} else {
Some(format!("{}^{}", n, e.1))
}
} else {
None
}
});
if let Some(alias) = alias {
write!(out, " ({})", alias).unwrap();
}
String::from_utf8(out).unwrap()
}
}
impl Context {
/// Wrapper around show that calls `println!`.
pub fn print(&self, value: &Number) {
println!("{}", self.show(value));
println!("{}", value.show(self));
}
/// Given a unit name, returns its value if it exists. Supports SI
@ -110,7 +60,7 @@ impl Context {
for &(ref pre, ref value) in &self.prefixes {
if name.starts_with(pre) {
if let Some(v) = self.lookup(&name[pre.len()..]) {
return Some(v.mul(&value))
return Some((&v * &value).unwrap())
}
}
}
@ -188,36 +138,8 @@ impl Context {
match *expr {
Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)),
Expr::Const(ref num, ref frac, ref exp) => {
use std::str::FromStr;
let num = Mpz::from_str_radix(&*num, 10).unwrap();
let frac = if let &Some(ref frac) = frac {
let frac_digits = frac.len();
let frac = Mpz::from_str_radix(&*frac, 10).unwrap();
Mpq::ratio(&frac, &Mpz::from(10).pow(frac_digits as u32))
} else {
Mpq::zero()
};
let exp = if let &Some(ref exp) = exp {
let exp: i32 = match FromStr::from_str(&*exp) {
Ok(exp) => exp,
// presumably because it is too large
Err(e) => return Err(format!("Failed to parse exponent: {}", e))
};
let res = Mpz::from(10).pow(exp.abs() as u32);
if exp < 0 {
Mpq::ratio(&Mpz::one(), &res)
} else {
Mpq::ratio(&res, &Mpz::one())
}
} else {
Mpq::one()
};
let num = &Mpq::ratio(&num, &Mpz::one()) + &frac;
let num = &num * &exp;
Ok(Number::new(num))
},
Expr::Const(ref num, ref frac, ref exp) =>
Number::from_parts(num, frac.as_ref().map(AsRef::as_ref), exp.as_ref().map(AsRef::as_ref)),
Expr::Date(ref date) => {
use chrono::format::Parsed;
use chrono::{DateTime, UTC, FixedOffset};
@ -250,31 +172,28 @@ impl Context {
}),
Expr::Plus(ref expr) => self.eval(&**expr),
Expr::Frac(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(bottom)) => {
if bottom.0 == Mpq::zero() {
return Err(format!("Division by zero: {} / {}", self.show(&top), self.show(&bottom)))
}
Ok(top.mul(&bottom.invert()))
},
(Ok(top), Ok(bottom)) =>
(&top / &bottom).ok_or_else(|| {
format!("Division by zero: {} / {}", top.show(self), bottom.show(self))
}),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
Expr::Add(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(bottom)) => {
top.add(&bottom).ok_or_else(|| {
(&top + &bottom).ok_or_else(|| {
format!("Add of values with differing units is not meaningful: {} + {}",
self.show(&top), self.show(&bottom))
top.show(self), bottom.show(self))
})
},
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
Expr::Sub(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(mut bottom)) => {
bottom.0 = -bottom.0;
top.add(&bottom).ok_or_else(|| {
(Ok(top), Ok(bottom)) => {
(&top - &bottom).ok_or_else(|| {
format!("Sub of values with differing units is not meaningful: {} - {}",
self.show(&top), self.show(&bottom))
top.show(self), bottom.show(self))
})
},
(Err(e), _) => Err(e),
@ -283,29 +202,13 @@ impl Context {
Expr::Mul(ref args) => args.iter().fold(Ok(Number::one()), |a, b| {
a.and_then(|a| {
let b = try!(self.eval(b));
Ok(a.mul(&b))
Ok((&a * &b).unwrap())
})
}),
Expr::Pow(ref base, ref exp) => {
let base = try!(self.eval(&**base));
let exp = try!(self.eval(&**exp));
let fexp: f64 = exp.0.into();
if exp.1.len() != 0 {
Err(format!("Exponent not dimensionless"))
} else if fexp.trunc() == fexp {
let iexp = fexp as i32;
if iexp < 0 && base.0 == Mpq::zero() {
return Err(format!("Disivion by zero: {}^{}", self.show(&base), iexp))
}
Ok(base.pow(fexp as i32))
} else if (1.0 / fexp).trunc() == 1.0 / fexp {
if base.0 < Mpq::zero() {
return Err(format!("Root of a negative number is imaginary, which is not yet implemented: {}^{}", self.show(&base), fexp))
}
base.root((1.0 / fexp) as i32).ok_or(format!("Unit roots must result in integer dimensions"))
} else {
Err(format!("Exponent not integer"))
}
Expr::Pow(ref base, ref exp) => match (self.eval(&**base), self.eval(&**exp)) {
(Ok(base), Ok(exp)) => base.pow(&exp),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
Expr::Convert(_, _) => Err(format!("Conversions (->) must be top-level expressions")),
Expr::Error(ref e) => Err(e.clone()),
@ -385,8 +288,8 @@ impl Context {
topu.0 = Mpq::one();
let mut bottomu = bottom.clone();
bottomu.0 = Mpq::one();
let left = self.show(&topu);
let right = self.show(&bottomu);
let left = topu.show(self);
let right = bottomu.show(self);
if self.short_output {
writeln!(buf, "Conformance error [ {left} || {right} ]",
left=left, right=right).unwrap();
@ -396,7 +299,7 @@ impl Context {
"{:>width$}: {right}"),
"Left side", "Right side", left=left, right=right, width=width).unwrap();
}
let diff = topu.mul(&bottomu.invert());
let diff = (&topu / &bottomu).unwrap();
let (recip, desc) = self.describe_unit(&diff.invert());
let word = match recip {
false => "multiply",
@ -468,7 +371,7 @@ impl Context {
},
_ => {
let val = try!(self.eval(expr));
Ok(self.show(&val))
Ok(val.show(self))
},
}
}

View file

@ -1,6 +1,8 @@
use gmp::mpq::Mpq;
use gmp::mpz::Mpz;
use std::collections::BTreeMap;
use eval::Show;
use std::ops::{Add, Div, Mul, Neg, Sub};
/// Number type
pub type Num = Mpq;
@ -133,30 +135,47 @@ impl Number {
Number(num, map)
}
pub fn from_parts(integer: &str, frac: Option<&str>, exp: Option<&str>) -> Result<Number, String> {
use std::str::FromStr;
let num = Mpz::from_str_radix(integer, 10).unwrap();
let frac = if let Some(ref frac) = frac {
let frac_digits = frac.len();
let frac = Mpz::from_str_radix(&*frac, 10).unwrap();
Mpq::ratio(&frac, &Mpz::from(10).pow(frac_digits as u32))
} else {
Mpq::zero()
};
let exp = if let Some(ref exp) = exp {
let exp: i32 = match FromStr::from_str(&*exp) {
Ok(exp) => exp,
// presumably because it is too large
Err(e) => return Err(format!("Failed to parse exponent: {}", e))
};
let res = Mpz::from(10).pow(exp.abs() as u32);
if exp < 0 {
Mpq::ratio(&Mpz::one(), &res)
} else {
Mpq::ratio(&res, &Mpz::one())
}
} else {
Mpq::one()
};
let num = &Mpq::ratio(&num, &Mpz::one()) + &frac;
let num = &num * &exp;
Ok(Number::new(num))
}
/// Computes the reciprocal (1/x) of the value.
pub fn invert(&self) -> Number {
Number(&one() / &self.0,
self.1.iter()
.map(|(&k, &power)| (k, -power))
.collect::<Unit>())
}
/// Adds two values. They must have matching units.
pub fn add(&self, other: &Number) -> Option<Number> {
if self.1 != other.1 {
return None
}
Some(Number(&self.0 + &other.0, self.1.clone()))
}
/// Multiplies two values, also multiplying their units.
pub fn mul(&self, other: &Number) -> Number {
let val = ::btree_merge(&self.1, &other.1, |a, b| if a+b != 0 { Some(a + b) } else { None });
Number(&self.0 * &other.0, val)
self.1.iter()
.map(|(&k, &power)| (k, -power))
.collect::<Unit>())
}
/// Raises a value to a dimensionless integer power.
pub fn pow(&self, exp: i32) -> Number {
pub fn powi(&self, exp: i32) -> Number {
let unit = self.1.iter()
.map(|(&k, &power)| (k, power * exp as i64))
.collect::<Unit>();
@ -176,4 +195,142 @@ impl Number {
}
Some(Number(root(&self.0, exp), res))
}
pub fn pow(&self, exp: &Number) -> Result<Number, String> {
use std::convert::Into;
if exp.1.len() != 0 {
return Err(format!("Exponent must be dimensionless"))
}
let mut exp = exp.0.clone();
exp.canonicalize();
let num = exp.get_num();
let den = exp.get_den();
let one = Mpz::one();
if den == one {
let exp: Option<i64> = (&num).into();
Ok(self.powi(exp.unwrap() as i32))
} else if num == one {
let exp: Option<i64> = (&den).into();
self.root(exp.unwrap() as i32).ok_or(format!(
"Unit roots must be in integer dimensions, i.e. you \
can only take the nth root of a unit to the nth \
power"))
} else {
Err(format!("Exponent must be either an integer or the reciprocal of an integer"))
}
}
}
impl Show for Number {
fn show(&self, context: &::eval::Context) -> String {
use std::io::Write;
let mut out = vec![];
let mut frac = vec![];
let mut value = self.clone();
value.0.canonicalize();
let (exact, approx) = match to_string(&value.0) {
(true, v) => (v, None),
(false, v) => if value.0.get_den() > Mpz::from(1_000_000) || value.0.get_num() > Mpz::from(1_000_000_000u64) {
(format!("approx. {}", v), None)
} else {
(format!("{:?}", value.0), Some(v))
}
};
write!(out, "{}", exact).unwrap();
if let Some(approx) = approx {
write!(out, ", approx. {}", approx).unwrap();
}
for (&dim, &exp) in &value.1 {
if exp < 0 {
frac.push((dim, exp));
} else {
write!(out, " {}", context.dimensions[dim]).unwrap();
if exp != 1 {
write!(out, "^{}", exp).unwrap();
}
}
}
if frac.len() > 0 {
write!(out, " /").unwrap();
for (dim, exp) in frac {
let exp = -exp;
write!(out, " {}", context.dimensions[dim]).unwrap();
if exp != 1 {
write!(out, "^{}", exp).unwrap();
}
}
}
let alias = context.aliases.get(&value.1).cloned().or_else(|| {
if value.1.len() == 1 {
let e = value.1.iter().next().unwrap();
let ref n = context.dimensions[*e.0];
if *e.1 == 1 {
Some(n.clone())
} else {
Some(format!("{}^{}", n, e.1))
}
} else {
None
}
});
if let Some(alias) = alias {
write!(out, " ({})", alias).unwrap();
}
String::from_utf8(out).unwrap()
}
}
impl<'a, 'b> Add<&'b Number> for &'a Number {
type Output = Option<Number>;
fn add(self, other: &Number) -> Self::Output {
if self.1 != other.1 {
return None
}
Some(Number(&self.0 + &other.0, self.1.clone()))
}
}
impl<'a, 'b> Sub<&'b Number> for &'a Number {
type Output = Option<Number>;
fn sub(self, other: &Number) -> Self::Output {
if self.1 != other.1 {
return None
}
Some(Number(&self.0 - &other.0, self.1.clone()))
}
}
impl<'a> Neg for &'a Number {
type Output = Option<Number>;
fn neg(self) -> Self::Output {
Some(Number(-&self.0, self.1.clone()))
}
}
impl<'a, 'b> Mul<&'b Number> for &'a Number {
type Output = Option<Number>;
fn mul(self, other: &Number) -> Self::Output {
let val = ::btree_merge(&self.1, &other.1, |a, b| if a+b != 0 { Some(a + b) } else { None });
Some(Number(&self.0 * &other.0, val))
}
}
impl<'a, 'b> Div<&'b Number> for &'a Number {
type Output = Option<Number>;
fn div(self, other: &Number) -> Self::Output {
if self.0 == zero() {
None
} else {
self * &other.invert()
}
}
}