Add output of computed derived units

E.g. a calculation with a result of W^2 will show as 1 watt^2 = kg^2 m^4 / s^6
This commit is contained in:
Tiffany Bennett 2016-08-21 21:25:49 -04:00
parent 6432f0b864
commit a5ddd614bc
3 changed files with 89 additions and 25 deletions

View file

@ -21,6 +21,7 @@ pub struct Context {
pub dimensions: Vec<Rc<String>>,
pub units: HashMap<String, Number>,
pub aliases: HashMap<Unit, String>,
pub reverse: HashMap<Unit, String>,
pub prefixes: Vec<(String, Number)>,
pub datepatterns: Vec<Vec<DatePattern>>,
pub short_output: bool,
@ -570,7 +571,7 @@ impl Context {
let aliases = self.aliases.iter()
.map(|(a, b)| (a.clone(), Rc::new(b.clone())))
.collect::<BTreeMap<_, _>>();
let results = factorize(self, &val, &aliases);
let results = factorize(&val, &aliases);
let mut results = results.into_sorted_vec();
results.dedup();
let results = results.into_iter().map(|Factors(_score, names)| {
@ -611,6 +612,7 @@ impl Context {
dimensions: Vec::new(),
units: HashMap::new(),
aliases: HashMap::new(),
reverse: HashMap::new(),
prefixes: Vec::new(),
datepatterns: Vec::new(),
short_output: false,
@ -759,6 +761,20 @@ impl Context {
(name, res)
});
let mut reverse = HashSet::new();
reverse.insert("newton");
reverse.insert("pascal");
reverse.insert("joule");
reverse.insert("watt");
reverse.insert("coulomb");
reverse.insert("volt");
reverse.insert("ohm");
reverse.insert("siemens");
reverse.insert("farad");
reverse.insert("weber");
reverse.insert("henry");
reverse.insert("tesla");
for (name, def) in udefs {
let name = match name {
Name::Unit(name) => (*name).clone(),
@ -772,6 +788,9 @@ impl Context {
},
Def::Unit(ref expr) => match ctx.eval(expr) {
Ok(Value::Number(v)) => {
if v.0 == Mpq::one() && reverse.contains(&*name) {
ctx.reverse.insert(v.1.clone(), name.clone());
}
ctx.units.insert(name.clone(), v);
},
Ok(_) => println!("Unit {} is not a number", name),

View file

@ -1,8 +1,8 @@
use std::collections::{BTreeMap, BinaryHeap};
use eval::Context;
use std::collections::{BTreeMap, BinaryHeap, HashMap};
use std::rc::Rc;
use number::{Number, Unit};
use std::cmp;
use gmp::mpq::Mpq;
#[derive(PartialEq, Eq, Debug)]
pub struct Factors(pub usize, pub Vec<Rc<String>>);
@ -19,7 +19,30 @@ impl cmp::Ord for Factors {
}
}
pub fn factorize(ctx: &Context, value: &Number, aliases: &BTreeMap<Unit, Rc<String>>)
pub fn fast_decompose(value: &Number, aliases: &HashMap<Unit, String>) -> Unit {
let mut best = None;
for (unit, name) in aliases.iter() {
let num = Number(Mpq::one(), unit.clone());
for &i in [-1, 1, 2].into_iter() {
let res = (value / &num.powi(i)).unwrap();
let score = res.complexity_score();
let better = best.as_ref().map(|&(_, _, _, current)| score < current).unwrap_or(true);
if better {
best = Some((name, unit, i, score));
}
}
}
if let Some((name, unit, pow, score)) = best {
if score < value.complexity_score() {
let mut res = (value / &Number(Mpq::one(), unit.clone()).powi(pow)).unwrap().1;
res.insert(Rc::new(name.clone()), pow as i64);
return res
}
}
value.1.clone()
}
pub fn factorize(value: &Number, aliases: &BTreeMap<Unit, Rc<String>>)
-> BinaryHeap<Factors> {
if value.1.len() == 0 {
let mut map = BinaryHeap::new();
@ -29,8 +52,6 @@ pub fn factorize(ctx: &Context, value: &Number, aliases: &BTreeMap<Unit, Rc<Stri
let mut candidates: BinaryHeap<Factors> = BinaryHeap::new();
let value_score = value.complexity_score();
for (unit, name) in aliases.iter().rev() {
use gmp::mpq::Mpq;
let res = (value / &Number(Mpq::one(), unit.clone())).unwrap();
//if res.1.len() >= value.1.len() {
let score = res.complexity_score();
@ -38,7 +59,7 @@ pub fn factorize(ctx: &Context, value: &Number, aliases: &BTreeMap<Unit, Rc<Stri
if score >= value_score {
continue
}
let res = factorize(ctx, &res, aliases);
let res = factorize(&res, aliases);
for Factors(score, mut vec) in res {
vec.push(name.clone());
vec.sort();

View file

@ -247,30 +247,15 @@ impl Number {
String::from_utf8(out).unwrap()
}
pub fn complexity_score(&self) -> i64 {
self.1.iter().map(|(_, p)| 1 + p.abs()).fold(0, |a,x| a+x)
}
}
impl fmt::Debug for Number {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.show_number_part())
}
}
impl Show for Number {
fn show(&self, context: &::eval::Context) -> String {
fn unit_to_string(unit: &Unit) -> String {
use std::io::Write;
let mut out = vec![];
let mut frac = vec![];
let mut value = self.clone();
value.0.canonicalize();
write!(out, "{}", self.show_number_part()).unwrap();
for (dim, &exp) in &value.1 {
for (dim, &exp) in unit {
if exp < 0 {
frac.push((dim.clone(), exp));
frac.push((dim, exp));
} else {
write!(out, " {}", dim).unwrap();
if exp != 1 {
@ -288,6 +273,45 @@ impl Show for Number {
}
}
}
out.remove(0);
String::from_utf8(out).unwrap()
}
pub fn unit_name(&self, context: &::eval::Context) -> String {
let raw = Number::unit_to_string(&self.1);
let pretty = ::factorize::fast_decompose(self, &context.reverse);
if self.1 != pretty {
let pretty = Number::unit_to_string(&pretty);
format!("{} = {}", pretty, raw)
} else {
raw
}
}
pub fn complexity_score(&self) -> i64 {
self.1.iter().map(|(_, p)| 1 + p.abs()).fold(0, |a,x| a+x)
}
}
impl fmt::Debug for Number {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.show_number_part())
}
}
impl Show for Number {
fn show(&self, context: &::eval::Context) -> String {
use std::io::Write;
let mut out = vec![];
let mut value = self.clone();
value.0.canonicalize();
write!(out, "{} {}", self.show_number_part(), self.unit_name(context)).unwrap();
let alias = context.aliases.get(&value.1).cloned().or_else(|| {
if value.1.len() == 1 {
let e = value.1.iter().next().unwrap();