Merge branch 'gmp'

This commit is contained in:
Tiffany Bennett 2016-08-07 13:56:34 -04:00
commit 4582a9e024
6 changed files with 437 additions and 112 deletions

View file

@ -13,6 +13,7 @@ keywords = ["unit", "math", "conversion", "cli", "tool"]
default = ["rustyline"]
[dependencies]
rust-gmp = { git = "https://github.com/tiffany352/rust-gmp.git" }
rustyline = { version = "0.2.3", optional = true }
irc = { version = "0.11.3", optional = true }

View file

@ -25,7 +25,7 @@ fn main() {
};
let line = message_str[prefix.len()..].trim();
let reply = match one_line(&mut ctx, line) {
Ok(v) => ctx.show(&v),
Ok(v) => v,
Err(e) => e
};
let mut i = 0;

View file

@ -23,7 +23,7 @@ fn main() {
Ok(line) => {
rl.add_history_entry(&line);
match one_line(&mut ctx, &*line) {
Ok(v) => ctx.print(&v),
Ok(v) => println!("{}", v),
Err(e) => println!("{}", e)
};
},
@ -57,7 +57,7 @@ fn main() {
Err(_) => return
};
match one_line(&mut ctx, &*line) {
Ok(v) => ctx.print(&v),
Ok(v) => println!("{}", v),
Err(e) => println!("{}", e)
};
line.clear();

View file

@ -1,6 +1,10 @@
use std::collections::HashMap;
use std::collections::BTreeMap;
use gmp::mpz::Mpz;
use gmp::mpq::Mpq;
/// Number type
pub type Num = Mpq;
/// A simple alias to add semantic meaning for when we pass around dimension IDs.
pub type Dim = usize;
/// Alias for the primary representation of dimensionality.
@ -8,7 +12,7 @@ pub type Unit = BTreeMap<Dim, i64>;
/// The basic representation of a number with a unit.
#[derive(Clone)]
pub struct Value(f64, Unit);
pub struct Value(Num, Unit);
/// The evaluation context that contains unit definitions.
pub struct Context {
@ -19,14 +23,147 @@ pub struct Context {
pub short_output: bool,
}
fn one() -> Mpq {
Mpq::one()
}
fn zero() -> Mpq {
Mpq::zero()
}
fn pow(left: &Mpq, exp: i32) -> Mpq {
if exp < 0 {
one() / pow(left, -exp)
} else {
let num = left.get_num().pow(exp as u32);
let den = left.get_den().pow(exp as u32);
Mpq::ratio(&num, &den)
}
}
fn root(left: &Mpq, n: i32) -> Mpq {
if n < 0 {
one() / root(left, -n)
} else {
let num = left.get_num().root(n as u32);
let den = left.get_den().root(n as u32);
Mpq::ratio(&num, &den)
}
}
fn to_string(rational: &Mpq) -> (bool, String) {
use std::char::from_digit;
let sign = *rational < Mpq::zero();
let rational = rational.abs();
let num = rational.get_num();
let den = rational.get_den();
let intdigits = (&num / &den).size_in_base(10) as u32;
let mut buf = String::new();
if sign {
buf.push('-');
}
let zero = Mpq::zero();
let one = Mpz::one();
let ten = Mpz::from(10);
let ten_mpq = Mpq::ratio(&ten, &one);
let mut cursor = rational / Mpq::ratio(&ten.pow(intdigits), &one);
let mut n = 0;
let mut only_zeros = true;
let mut zeros = 0;
let mut placed_decimal = false;
loop {
let exact = cursor == zero;
let use_sci = intdigits+zeros > 9;
let placed_ints = n >= intdigits;
let bail =
(exact && (placed_ints || use_sci)) ||
(n as i32 - zeros as i32 > 6 && use_sci) ||
n as i32 - zeros as i32 > ::std::cmp::max(intdigits as i32, 6);
if bail && use_sci {
// scientific notation
buf = buf[zeros as usize + placed_decimal as usize + sign as usize..].to_owned();
buf.insert(1, '.');
if buf.len() == 2 {
buf.insert(2, '0');
}
if sign {
buf.insert(0, '-');
}
buf.push_str(&*format!("e{}", intdigits as i32 - zeros as i32 - 1));
return (exact, buf)
}
if bail {
return (exact, buf)
}
if n == intdigits {
buf.push('.');
placed_decimal = true;
}
let digit = &(&(&cursor.get_num() * &ten) / &cursor.get_den()) % &ten;
let v: Option<i64> = (&digit).into();
let v = v.unwrap();
if v != 0 {
only_zeros = false
} else if only_zeros {
zeros += 1;
}
if !(v == 0 && only_zeros && n < intdigits-1) {
buf.push(from_digit(v as u32, 10).unwrap());
}
cursor = &cursor * &ten_mpq;
cursor = &cursor - &Mpq::ratio(&digit, &one);
n += 1;
}
}
fn btree_merge<K: ::std::cmp::Ord+Clone, V:Clone, F:Fn(&V, &V) -> Option<V>>(
left: &BTreeMap<K, V>, right: &BTreeMap<K, V>, merge_func: F
) -> BTreeMap<K, V> {
let mut res = BTreeMap::new();
let mut a = left.iter().peekable();
let mut b = right.iter().peekable();
loop {
match (a.peek().cloned(), b.peek().cloned()) {
(Some((akey, aval)), Some((bkey, bval))) if akey == bkey => {
if let Some(v) = merge_func(aval, bval) {
res.insert(akey.clone(), v);
}
a.next();
b.next();
},
(Some((akey, _)), Some((bkey, bval))) if akey > bkey => {
res.insert(bkey.clone(), bval.clone());
b.next();
},
(Some((akey, aval)), Some((bkey, _))) if akey < bkey => {
res.insert(akey.clone(), aval.clone());
a.next();
},
(Some(_), Some(_)) => panic!(),
(None, Some((bkey, bval))) => {
res.insert(bkey.clone(), bval.clone());
b.next();
},
(Some((akey, aval)), None) => {
res.insert(akey.clone(), aval.clone());
a.next();
},
(None, None) => break,
}
}
res
}
impl Value {
/// Creates a dimensionless value.
pub fn new(num: f64) -> Value {
pub fn new(num: Num) -> Value {
Value(num, Unit::new())
}
/// Creates a value with a single dimension.
pub fn new_unit(num: f64, unit: Dim) -> Value {
pub fn new_unit(num: Num, unit: Dim) -> Value {
let mut map = Unit::new();
map.insert(unit, 1);
Value(num, map)
@ -34,7 +171,7 @@ impl Value {
/// Computes the reciprocal (1/x) of the value.
pub fn invert(&self) -> Value {
Value(1.0 / self.0,
Value(&one() / &self.0,
self.1.iter()
.map(|(&k, &power)| (k, -power))
.collect::<Unit>())
@ -45,58 +182,21 @@ impl Value {
if self.1 != other.1 {
return None
}
Some(Value(self.0 + other.0, self.1.clone()))
Some(Value(&self.0 + &other.0, self.1.clone()))
}
/// Multiplies two values, also multiplying their units.
pub fn mul(&self, other: &Value) -> Value {
let mut val = Unit::new();
let mut a = self.1.iter().peekable();
let mut b = other.1.iter().peekable();
loop {
match (a.peek().cloned(), b.peek().cloned()) {
(Some((ka, pa)), Some((kb, pb))) if ka == kb => {
// merge
let power = pa+pb;
if power != 0 {
val.insert(ka.clone(), power);
}
a.next();
b.next();
},
(Some((ka, _)), Some((kb, vb))) if ka > kb => {
// push vb, advance
val.insert(kb.clone(), vb.clone());
b.next();
},
(Some((ka, va)), Some((kb, _))) if ka < kb => {
// push va, advance
val.insert(ka.clone(), va.clone());
a.next();
},
(Some(_), Some(_)) => panic!(),
(None, Some((kb, vb))) => {
// push vb, advance
val.insert(kb.clone(), vb.clone());
b.next();
},
(Some((ka, va)), None) => {
// push va, advance
val.insert(ka.clone(), va.clone());
a.next();
},
(None, None) => break
}
}
Value(self.0 * other.0, val)
let val = btree_merge(&self.1, &other.1, |a, b| if a+b != 0 { Some(a + b) } else { None });
Value(&self.0 * &other.0, val)
}
/// Raises a value to a dimensionless integer power.
pub fn pow(&self, exp: i32) -> Value {
Value(self.0.powi(exp),
self.1.iter()
.map(|(&k, &power)| (k, power * exp as i64))
.collect::<Unit>())
let unit = self.1.iter()
.map(|(&k, &power)| (k, power * exp as i64))
.collect::<Unit>();
Value(pow(&self.0, exp), unit)
}
/// Computes the nth root of a value iff all of its units have
@ -110,7 +210,7 @@ impl Value {
res.insert(dim, power / exp as i64);
}
}
Some(Value(self.0.powf(1.0 / exp as f64), res))
Some(Value(root(&self.0, exp), res))
}
}
@ -123,7 +223,22 @@ impl Context {
let mut out = vec![];
let mut frac = vec![];
write!(out, "{}", value.0).unwrap();
let mut value = value.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));
@ -144,10 +259,21 @@ impl Context {
}
}
}
if let Some(name) = self.aliases.get(&value.1) {
write!(out, " ({})", name).unwrap();
} else if value.1.len() == 1 {
write!(out, " ({})", self.dimensions[*value.1.iter().next().unwrap().0]).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()
}
@ -162,7 +288,7 @@ impl Context {
pub fn lookup(&self, name: &str) -> Option<Value> {
for (i, ref k) in self.dimensions.iter().enumerate() {
if name == *k {
return Some(Value::new_unit(1.0, i))
return Some(Value::new_unit(one(), i))
}
}
self.units.get(name).cloned().or_else(|| {
@ -180,7 +306,7 @@ impl Context {
}
for (unit, alias) in &self.aliases {
if name == alias {
return Some(Value(1.0, unit.clone()))
return Some(Value(one(), unit.clone()))
}
}
None
@ -245,21 +371,55 @@ impl Context {
(recip, String::from_utf8(buf).unwrap())
}
/// Evaluates an expression to compute its value, including `->`
/// Evaluates an expression to compute its value, *excluding* `->`
/// conversions.
pub fn eval(&self, expr: &::unit_defs::Expr) -> Result<Value, String> {
use unit_defs::Expr;
match *expr {
Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)),
Expr::Const(num) => Ok(Value::new(num)),
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 {
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(Value::new(num))
},
Expr::Neg(ref expr) => self.eval(&**expr).and_then(|mut v| {
v.0 = -v.0;
Ok(v)
}),
Expr::Plus(ref expr) => self.eval(&**expr),
Expr::Frac(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(bottom)) => Ok(top.mul(&bottom.invert())),
(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()))
},
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
@ -273,17 +433,106 @@ impl Context {
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
Expr::Convert(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(bottom)) => {
Expr::Mul(ref args) => args.iter().fold(Ok(Value::new(one())), |a, b| {
a.and_then(|a| {
let b = try!(self.eval(b));
Ok(a.mul(&b))
})
}),
Expr::Pow(ref base, ref exp) => {
let base = try!(self.eval(&**base));
let exp = try!(self.eval(&**exp));
let fexp: f64 = exp.0.to_f64();
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 {
base.root((1.0 / fexp) as i32).ok_or(format!("Unit roots must result in integer dimensions"))
} else {
Err(format!("Exponent not integer"))
}
},
Expr::Convert(_, _) => Err(format!("Conversions (->) must be top-level expressions")),
Expr::Error(ref e) => Err(e.clone()),
}
}
pub fn eval_unit_name(&self, expr: &::unit_defs::Expr) -> Result<BTreeMap<String, isize>, String> {
use unit_defs::Expr;
match *expr {
Expr::Unit(ref name) => {
let mut map = BTreeMap::new();
map.insert(name.clone(), 1);
Ok(map)
},
Expr::Const(ref x, None, None) if x == "1" || x == "-1" => Ok(BTreeMap::new()),
Expr::Const(_, _, _) => Err(format!("Constants are not allowed in the right hand side of conversions")),
Expr::Frac(ref left, ref right) => {
let left = try!(self.eval_unit_name(left));
let right = try!(self.eval_unit_name(right)).into_iter()
.map(|(k,v)| (k, -v)).collect::<BTreeMap<_, _>>();
Ok(btree_merge(&left, &right, |a,b| if a+b != 0 { Some(a + b) } else { None }))
},
Expr::Mul(ref args) => {
args[1..].iter().fold(self.eval_unit_name(&args[0]), |acc, b| {
let acc = try!(acc);
let b = try!(self.eval_unit_name(b));
Ok(btree_merge(&acc, &b, |a,b| if a+b != 0 { Some(a+b) } else { None }))
})
},
Expr::Pow(ref left, ref exp) => {
let res = try!(self.eval(exp));
if res.1.len() > 0 {
return Err(format!("Exponents must be dimensionless"))
}
Ok(try!(self.eval_unit_name(left)).into_iter()
.filter_map(|(k, v)| {
let v = v + res.0.to_f64() as isize;
if v != 0 {
Some((k, v))
} else {
None
}
})
.collect::<BTreeMap<_, _>>())
},
Expr::Add(ref left, ref right) => {
let left = try!(self.eval_unit_name(left));
let right = try!(self.eval_unit_name(right));
if left != right {
return Err(format!("Add of values with differing dimensions is not meaningful"))
}
Ok(left)
},
Expr::Neg(ref v) => self.eval_unit_name(v),
Expr::Plus(ref v) => self.eval_unit_name(v),
Expr::Convert(_, _) => Err(format!("Conversions are not allowed in the right hand of conversions")),
Expr::Error(ref e) => Err(e.clone()),
}
}
/// Evaluates an expression, include `->` conversions.
pub fn eval_outer(&self, expr: &::unit_defs::Expr) -> Result<String, String> {
use unit_defs::Expr;
match *expr {
Expr::Convert(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom), self.eval_unit_name(&**bottom)) {
(Ok(top), Ok(bottom), Ok(bottom_name)) => {
if top.1 != bottom.1 {
use std::io::Write;
let mut buf = vec![];
let width = 12;
let mut topu = top.clone();
topu.0 = 1.0;
topu.0 = one();
let mut bottomu = bottom.clone();
bottomu.0 = 1.0;
bottomu.0 = one();
let left = self.show(&topu);
let right = self.show(&bottomu);
if self.short_output {
@ -313,32 +562,62 @@ impl Context {
Err(String::from_utf8(buf).unwrap())
} else {
Ok(top.mul(&bottom.invert()))
let raw = &top.0 / &bottom.0;
let (raw_exact, raw) = to_string(&raw);
let approx = if raw_exact {
format!("")
} else {
format!("approx. ")
};
let mut unit_top = vec![];
let mut unit_frac = vec![];
for (name, exp) in bottom_name.into_iter() {
if exp < 0 {
unit_frac.push((name, -exp));
} else {
unit_top.push((name, exp));
}
}
let unit_top = unit_top.into_iter().fold(String::new(), |mut acc, (name, exp)| {
acc.push(' ');
acc.push_str(&*name);
if exp != 1 {
acc.push_str(&*format!("^{}", exp));
}
acc
});
let unit_frac = unit_frac.into_iter().fold(String::new(), |mut acc, (name, exp)| {
if acc.len() > 0 {
acc.push(' ');
}
acc.push_str(&*name);
if exp != 1 {
acc.push_str(&*format!("^{}", exp));
}
acc
});
let unit_frac = if unit_frac.len() > 0 {
format!(" / {}", unit_frac)
} else {
unit_frac
};
let reduced = match self.describe_unit(&bottom) {
(false, v) => v,
(true, v) => format!("1 / {}", v)
};
Ok(format!("{approx}{raw}{unit_top}{unit_frac} ({reduced})",
approx=approx, raw=raw, unit_top=unit_top,
unit_frac=unit_frac, reduced=reduced))
}
},
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
(Err(e), _, _) => Err(e),
(_, Err(e), _) => Err(e),
(_, _, Err(e)) => Err(e),
},
Expr::Mul(ref args) => args.iter().fold(Ok(Value::new(1.0)), |a, b| {
a.and_then(|a| {
let b = try!(self.eval(b));
Ok(a.mul(&b))
})
}),
Expr::Pow(ref base, ref exp) => {
let base = try!(self.eval(&**base));
let exp = try!(self.eval(&**exp));
if exp.1.len() != 0 {
Err(format!("Exponent not dimensionless"))
} else if exp.0.trunc() == exp.0 {
Ok(base.pow(exp.0 as i32))
} else if (1.0 / exp.0).trunc() == 1.0 / exp.0 {
base.root((1.0 / exp.0) as i32).ok_or(format!("Unit roots must result in integer dimensions"))
} else {
Err(format!("Exponent not integer"))
}
_ => {
let val = try!(self.eval(expr));
Ok(self.show(&val))
},
Expr::Error(ref e) => Err(e.clone()),
}
}

View file

@ -26,6 +26,8 @@ println!("{}", one_line(&mut ctx, "kWh / year -> W").unwrap());
```
*/
extern crate gmp;
pub mod unit_defs;
pub mod eval;
@ -102,8 +104,8 @@ pub fn load() -> Result<Context, String> {
}
/// Evaluates a single line within a context.
pub fn one_line(ctx: &mut Context, line: &str) -> Result<Value, String> {
pub fn one_line(ctx: &mut Context, line: &str) -> Result<String, String> {
let mut iter = unit_defs::TokenIterator::new(line.trim()).peekable();
let expr = unit_defs::parse_expr(&mut iter);
ctx.eval(&expr)
ctx.eval_outer(&expr)
}

View file

@ -1,6 +1,5 @@
use std::str::Chars;
use std::iter::Peekable;
use std::str::FromStr;
use std::rc::Rc;
#[derive(Debug, Clone)]
@ -8,7 +7,7 @@ pub enum Token {
Newline,
Comment(usize),
Ident(String),
Number(f64),
Number(String, Option<String>, Option<String>),
Slash,
ColonDash,
DColonDash,
@ -91,25 +90,69 @@ impl<'a> Iterator for TokenIterator<'a> {
_ => Token::Slash
},
x @ '0'...'9' | x @ '.' => {
let mut buf = String::new();
buf.push(x);
while let Some(c) = self.0.peek().cloned() {
match c {
'e' | 'E' => {
buf.push(self.0.next().unwrap());
loop {
match self.0.peek().cloned() {
Some('e') | Some('E') => self.0.next(),
_ => break
};
}
},
'0'...'9' | '.' | '-' | '+' => buf.push(self.0.next().unwrap()),
_ => break
use std::ascii::AsciiExt;
let mut integer = String::new();
let mut frac = None;
let mut exp = None;
// integer component
if x != '.' {
integer.push(x);
while let Some(c) = self.0.peek().cloned() {
match c {
'0'...'9' => integer.push(self.0.next().unwrap()),
_ => break
}
}
} else {
integer.push('0');
}
// fractional component
if x == '.' || Some('.') == self.0.peek().cloned() {
let mut buf = String::new();
if x != '.' {
self.0.next();
}
while let Some(c) = self.0.peek().cloned() {
match c {
'0'...'9' => buf.push(self.0.next().unwrap()),
_ => break
}
}
if buf.len() > 0 {
frac = Some(buf)
}
}
FromStr::from_str(&*buf).map(|x| Token::Number(x))
.unwrap_or(Token::Error(format!("Invalid number literal: `{}`", buf)))
// exponent
if let Some('e') = self.0.peek().cloned().map(|x| x.to_ascii_lowercase()) {
let mut buf = String::new();
self.0.next();
if let Some('e') = self.0.peek().cloned().map(|x| x.to_ascii_lowercase()) {
self.0.next();
}
if let Some(c) = self.0.peek().cloned() {
match c {
'-' => {
buf.push(self.0.next().unwrap());
},
'+' => {
self.0.next();
},
_ => ()
}
}
while let Some(c) = self.0.peek().cloned() {
match c {
'0'...'9' => buf.push(self.0.next().unwrap()),
_ => break
}
}
if buf.len() > 0 {
exp = Some(buf)
}
}
Token::Number(integer, frac, exp)
},
':' => match self.0.next() {
Some(':') => match self.0.next() {
@ -191,7 +234,7 @@ pub type Iter<'a> = Peekable<TokenIterator<'a>>;
#[derive(Debug, Clone)]
pub enum Expr {
Unit(String),
Const(f64),
Const(String, Option<String>, Option<String>),
Frac(Box<Expr>, Box<Expr>),
Mul(Vec<Expr>),
Pow(Box<Expr>, Box<Expr>),
@ -220,11 +263,11 @@ pub struct Defs {
fn parse_term(mut iter: &mut Iter) -> Expr {
match iter.next().unwrap() {
Token::Ident(name) => Expr::Unit(name),
Token::Number(num) => Expr::Const(num),
Token::Number(num, frac, exp) => Expr::Const(num, frac, exp),
Token::Plus => Expr::Plus(Box::new(parse_term(iter))),
Token::Minus => Expr::Neg(Box::new(parse_term(iter))),
// NYI: Imaginary numbers
Token::ImaginaryUnit => Expr::Const(0.0),
Token::ImaginaryUnit => Expr::Const("0".to_owned(), None, None),
Token::LPar => {
let res = parse_expr(iter);
match iter.next().unwrap() {