mirror of
https://github.com/tiffany352/rink-rs
synced 2024-11-10 13:44:15 +00:00
Merge branch 'float2'
This commit is contained in:
commit
efd3182f46
10 changed files with 446 additions and 110 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
use std::rc::Rc;
|
||||
use std::fmt;
|
||||
use gmp::mpq::Mpq;
|
||||
use number::Num;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SuffixOp {
|
||||
|
@ -31,7 +31,7 @@ pub enum DateToken {
|
|||
pub enum Expr {
|
||||
Unit(String),
|
||||
Quote(String),
|
||||
Const(Mpq),
|
||||
Const(Num),
|
||||
Date(Vec<DateToken>),
|
||||
Frac(Box<Expr>, Box<Expr>),
|
||||
Mul(Vec<Expr>),
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use number::{Dim, Number, Unit};
|
||||
use number::{Dim, Number, Unit, Num};
|
||||
use ast::{Expr, DatePattern};
|
||||
use gmp::mpq::Mpq;
|
||||
use search;
|
||||
|
||||
/// The evaluation context that contains unit definitions.
|
||||
|
@ -157,8 +156,8 @@ impl Context {
|
|||
|
||||
let mut buf = vec![];
|
||||
let mut recip = false;
|
||||
let square = Number(Mpq::one(), value.1.clone()).root(2).ok();
|
||||
let inverse = (&Number::one() / &Number(Mpq::one(), value.1.clone())).unwrap();
|
||||
let square = Number(Num::one(), value.1.clone()).root(2).ok();
|
||||
let inverse = (&Number::one() / &Number(Num::one(), value.1.clone())).unwrap();
|
||||
if let Some(name) = self.quantities.get(&value.1) {
|
||||
write!(buf, "{}", name).unwrap();
|
||||
} else if let Some(name) = square.and_then(|square| self.quantities.get(&square.1)) {
|
||||
|
|
|
@ -8,7 +8,7 @@ use xml::EventReader;
|
|||
use xml::reader::XmlEvent;
|
||||
use ast::{Defs, Def, Expr};
|
||||
use std::rc::Rc;
|
||||
use gmp::mpq::Mpq;
|
||||
use number::Num;
|
||||
|
||||
static URL: &'static str = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
|
||||
|
||||
|
@ -40,7 +40,7 @@ pub fn parse(f: File) -> Result<Defs, String> {
|
|||
if let Ok(num) = ::number::Number::from_parts(integer, frac, None) {
|
||||
out.push((currency.to_owned(), Rc::new(Def::Unit(
|
||||
Expr::Mul(vec![
|
||||
Expr::Frac(Box::new(Expr::Const(Mpq::one())),
|
||||
Expr::Frac(Box::new(Expr::Const(Num::one())),
|
||||
Box::new(Expr::Const(num))),
|
||||
Expr::Unit("EUR".to_string())
|
||||
]))), Some(format!("Sourced from European Central Bank."))));
|
||||
|
|
21
src/date.rs
21
src/date.rs
|
@ -6,7 +6,7 @@ use ast::{DatePattern, DateToken, show_datepattern};
|
|||
use chrono::format::Parsed;
|
||||
use chrono::{Weekday, DateTime, UTC, FixedOffset, Duration, Date};
|
||||
use context::Context;
|
||||
use number::{Number, Dim};
|
||||
use number::{Number, Num, Dim};
|
||||
use std::iter::Peekable;
|
||||
|
||||
pub fn parse_date<I>(
|
||||
|
@ -292,24 +292,17 @@ pub fn try_decode(date: &[DateToken], context: &Context) -> Result<DateTime<Fixe
|
|||
}
|
||||
|
||||
pub fn to_duration(num: &Number) -> Result<Duration, String> {
|
||||
use gmp::mpq::Mpq;
|
||||
use gmp::mpz::Mpz;
|
||||
|
||||
if num.1.len() != 1 || num.1.get("s") != Some(&1) {
|
||||
return Err(format!("Expected seconds"))
|
||||
}
|
||||
let max = Mpq::ratio(&Mpz::from(i64::max_value() / 1000), &Mpz::one());
|
||||
let max = Num::from(i64::max_value() / 1000);
|
||||
if num.0.abs() > max {
|
||||
return Err(format!("Implementation error: Number is out of range ({:?})", max))
|
||||
}
|
||||
let ms_div = Mpz::from(1_000);
|
||||
let ms = &(&num.0.get_num() * &ms_div) / &num.0.get_den();
|
||||
let ns_div = Mpz::from(1_000_000_000);
|
||||
let ns = &num.0 - &Mpq::ratio(&ms, &ms_div);
|
||||
let ns = &(&ns.get_num() * &ns_div) / &ns.get_den();
|
||||
let ms: Option<i64> = (&ms).into();
|
||||
let ns: Option<i64> = (&ns).into();
|
||||
Ok(Duration::milliseconds(ms.unwrap()) + Duration::nanoseconds(ns.unwrap()))
|
||||
let (ms, rem) = num.0.div_rem(&Num::from(1000));
|
||||
let ns = &rem / &Num::from(1_000_000_000);
|
||||
Ok(Duration::milliseconds(ms.to_int().unwrap()) +
|
||||
Duration::nanoseconds(ns.to_int().unwrap()))
|
||||
}
|
||||
|
||||
pub fn from_duration(duration: &Duration) -> Result<Number, String> {
|
||||
|
@ -322,7 +315,7 @@ pub fn from_duration(duration: &Duration) -> Result<Number, String> {
|
|||
let ns_div = Mpz::from(1_000_000_000);
|
||||
let ms = Mpq::ratio(&Mpz::from(ms), &ms_div);
|
||||
let ns = Mpq::ratio(&Mpz::from(ns), &ns_div);
|
||||
Ok(Number::new_unit(&ms + &ns, Dim::new("s")))
|
||||
Ok(Number::new_unit(Num::Mpq(&ms + &ns), Dim::new("s")))
|
||||
}
|
||||
|
||||
pub fn now() -> DateTime<FixedOffset> {
|
||||
|
|
189
src/eval.rs
189
src/eval.rs
|
@ -3,8 +3,7 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use gmp::mpq::Mpq;
|
||||
use number::{Number, Dim, NumberParts, pow};
|
||||
use number::{Number, Num, Int, Dim, NumberParts, pow};
|
||||
use date;
|
||||
use ast::{Expr, SuffixOp, Query, Conversion};
|
||||
use std::rc::Rc;
|
||||
|
@ -17,7 +16,6 @@ use reply::{
|
|||
};
|
||||
use search;
|
||||
use context::Context;
|
||||
use gmp::mpz::Mpz;
|
||||
|
||||
impl Context {
|
||||
/// Evaluates an expression to compute its value, *excluding* `->`
|
||||
|
@ -93,19 +91,149 @@ impl Context {
|
|||
}),
|
||||
Expr::Equals(_, ref right) => self.eval(right),
|
||||
Expr::Call(ref name, ref args) => {
|
||||
let args = try!(args.iter().map(|x| self.eval(x)).collect::<Result<Vec<_>, _>>());
|
||||
let args = try!(
|
||||
args.iter()
|
||||
.map(|x| self.eval(x))
|
||||
.collect::<Result<Vec<_>, _>>());
|
||||
|
||||
macro_rules! func {
|
||||
(fn $fname:ident($($name:ident : $ty:ident),*) $block:block) => {{
|
||||
let mut iter = args.iter();
|
||||
let mut count = 0;
|
||||
$( count += 1; let _ = stringify!($name); )*;
|
||||
$(
|
||||
let $name = match iter.next() {
|
||||
Some(&Value::$ty(ref v)) => v,
|
||||
Some(x) => return Err(format!(
|
||||
"Expected {}, got <{}>",
|
||||
stringify!($ty), x.show(self))),
|
||||
None => return Err(format!(
|
||||
"Argument number mismatch for {}: \
|
||||
Expected {}, got {}",
|
||||
stringify!($fname), count, args.len()))
|
||||
};
|
||||
)*;
|
||||
if iter.next().is_some() {
|
||||
return Err(format!(
|
||||
"Argument number mismatch for {}: \
|
||||
Expected {}, got {}",
|
||||
stringify!($fname), count, args.len()));
|
||||
}
|
||||
let res: Result<Value, String> = {
|
||||
$block
|
||||
};
|
||||
res.map_err(|e| {
|
||||
format!(
|
||||
"{}: {}({})",
|
||||
e, stringify!($fname),
|
||||
args.iter()
|
||||
.map(|x| x.show(self))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "))
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
||||
match &**name {
|
||||
"sqrt" => {
|
||||
if args.len() != 1 {
|
||||
return Err(format!("Argument number mismatch for sqrt: expected 1, got {}", args.len()))
|
||||
"sqrt" => func!(fn sqrt(num: Number) {
|
||||
num.root(2).map(Value::Number)
|
||||
}),
|
||||
"exp" => func!(fn exp(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().exp()), num.1.clone())))
|
||||
}),
|
||||
"ln" => func!(fn ln(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().ln()), num.1.clone())))
|
||||
}),
|
||||
"log" => func!(fn log(num: Number, base: Number) {
|
||||
if base.1.len() > 0 {
|
||||
Err(format!(
|
||||
"Base must be dimensionless"))
|
||||
} else {
|
||||
Ok(Value::Number(Number(
|
||||
Num::Float(num.0.to_f64().log(base.0.to_f64())),
|
||||
num.1.clone())))
|
||||
}
|
||||
match args[0] {
|
||||
Value::Number(ref num) =>
|
||||
num.root(2).map(Value::Number).map_err(|e| format!(
|
||||
"{}: sqrt <{}>", e, num.show(self))),
|
||||
ref x => Err(format!("Expected number, got <{}>", x.show(self)))
|
||||
}),
|
||||
"log2" => func!(fn log2(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().log2()), num.1.clone())))
|
||||
}),
|
||||
"log10" => func!(fn ln(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().log10()), num.1.clone())))
|
||||
}),
|
||||
"hypot" => func!(fn hypot(x: Number, y: Number) {
|
||||
if x.1 != y.1 {
|
||||
Err(format!(
|
||||
"Arguments to hypot must have matching \
|
||||
dimensionality"))
|
||||
} else {
|
||||
Ok(Value::Number(Number(
|
||||
Num::Float(x.0.to_f64().hypot(y.0.to_f64())),
|
||||
x.1.clone())))
|
||||
}
|
||||
},
|
||||
}),
|
||||
"sin" => func!(fn sin(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().sin()), num.1.clone())))
|
||||
}),
|
||||
"cos" => func!(fn cos(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().cos()), num.1.clone())))
|
||||
}),
|
||||
"tan" => func!(fn tan(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().tan()), num.1.clone())))
|
||||
}),
|
||||
"asin" => func!(fn asin(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().asin()), num.1.clone())))
|
||||
}),
|
||||
"acos" => func!(fn acos(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().acos()), num.1.clone())))
|
||||
}),
|
||||
"atan" => func!(fn atan(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().atan()), num.1.clone())))
|
||||
}),
|
||||
"atan2" => func!(fn atan2(x: Number, y: Number) {
|
||||
if x.1 != y.1 {
|
||||
Err(format!(
|
||||
"Arguments to atan2 must have matching \
|
||||
dimensionality"))
|
||||
} else {
|
||||
Ok(Value::Number(Number(
|
||||
Num::Float(x.0.to_f64().atan2(y.0.to_f64())),
|
||||
x.1.clone())))
|
||||
}
|
||||
}),
|
||||
"sinh" => func!(fn sinh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().sinh()), num.1.clone())))
|
||||
}),
|
||||
"cosh" => func!(fn cosh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().cosh()), num.1.clone())))
|
||||
}),
|
||||
"tanh" => func!(fn tanh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().tanh()), num.1.clone())))
|
||||
}),
|
||||
"asinh" => func!(fn asinh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().asinh()), num.1.clone())))
|
||||
}),
|
||||
"acosh" => func!(fn acosh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().acosh()), num.1.clone())))
|
||||
}),
|
||||
"atanh" => func!(fn atanh(num: Number) {
|
||||
Ok(Value::Number(Number(Num::Float(
|
||||
num.0.to_f64().atanh()), num.1.clone())))
|
||||
}),
|
||||
_ => Err(format!("Function not found: {}", name))
|
||||
}
|
||||
},
|
||||
|
@ -113,13 +241,13 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn eval_unit_name(&self, expr: &Expr) -> Result<(BTreeMap<String, isize>, Mpq), String> {
|
||||
pub fn eval_unit_name(&self, expr: &Expr) -> Result<(BTreeMap<String, isize>, Num), String> {
|
||||
match *expr {
|
||||
Expr::Equals(ref left, ref _right) => match **left {
|
||||
Expr::Unit(ref name) => {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(name.clone(), 1);
|
||||
Ok((map, Mpq::one()))
|
||||
Ok((map, Num::one()))
|
||||
},
|
||||
ref x => Err(format!("Expected identifier, got {:?}", x))
|
||||
},
|
||||
|
@ -127,7 +255,7 @@ impl Context {
|
|||
Expr::Unit(ref name) | Expr::Quote(ref name) => {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(self.canonicalize(&**name).unwrap_or_else(|| name.clone()), 1);
|
||||
Ok((map, Mpq::one()))
|
||||
Ok((map, Num::one()))
|
||||
},
|
||||
Expr::Const(ref i) =>
|
||||
Ok((BTreeMap::new(), i.clone())),
|
||||
|
@ -156,7 +284,7 @@ impl Context {
|
|||
if res.1.len() > 0 {
|
||||
return Err(format!("Exponents must be dimensionless"))
|
||||
}
|
||||
let res: f64 = res.0.into();
|
||||
let res = res.0.to_f64();
|
||||
let (left, lv) = try!(self.eval_unit_name(left));
|
||||
Ok((left.into_iter()
|
||||
.filter_map(|(k, v)| {
|
||||
|
@ -189,9 +317,9 @@ impl Context {
|
|||
|
||||
fn conformance_err(&self, top: &Number, bottom: &Number) -> ConformanceError {
|
||||
let mut topu = top.clone();
|
||||
topu.0 = Mpq::one();
|
||||
topu.0 = Num::one();
|
||||
let mut bottomu = bottom.clone();
|
||||
bottomu.0 = Mpq::one();
|
||||
bottomu.0 = Num::one();
|
||||
let mut suggestions = vec![];
|
||||
let diff = (&topu * &bottomu).unwrap();
|
||||
if diff.1.len() == 0 {
|
||||
|
@ -224,23 +352,24 @@ impl Context {
|
|||
raw: &Number,
|
||||
bottom: &Number,
|
||||
bottom_name: BTreeMap<String, isize>,
|
||||
bottom_const: Mpq,
|
||||
bottom_const: Num,
|
||||
base: u8
|
||||
) -> ConversionReply {
|
||||
let (exact, approx) = raw.numeric_value(base);
|
||||
let bottom_name = bottom_name.into_iter().map(
|
||||
|(a,b)| (Dim::new(&*a), b as i64)).collect();
|
||||
let (num, den) = bottom_const.to_rational();
|
||||
ConversionReply {
|
||||
value: NumberParts {
|
||||
exact_value: exact,
|
||||
approx_value: approx,
|
||||
factor: if bottom_const.get_num() != Mpz::one() {
|
||||
Some(format!("{}", bottom_const.get_num()))
|
||||
factor: if num != Int::one() {
|
||||
Some(format!("{}", num))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
divfactor: if bottom_const.get_den() != Mpz::one() {
|
||||
Some(format!("{}", bottom_const.get_den()))
|
||||
divfactor: if den != Int::one() {
|
||||
Some(format!("{}", den))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
@ -278,14 +407,12 @@ impl Context {
|
|||
let mut out = vec![];
|
||||
let len = units.len();
|
||||
for (i, unit) in units.into_iter().enumerate() {
|
||||
let res = &value / &unit.0;
|
||||
let div = &res.get_num() / res.get_den();
|
||||
let rem = &value - &(&unit.0 * &Mpq::ratio(&div, &Mpz::one()));
|
||||
value = rem;
|
||||
if i == len-1 {
|
||||
out.push(res);
|
||||
out.push(&value / &unit.0);
|
||||
} else {
|
||||
out.push(Mpq::ratio(&div, &Mpz::one()));
|
||||
let (div, rem) = value.div_rem(&unit.0);
|
||||
out.push(div);
|
||||
value = rem;
|
||||
}
|
||||
}
|
||||
Ok(list.into_iter().zip(out.into_iter()).map(|(name, value)| {
|
||||
|
@ -479,7 +606,7 @@ impl Context {
|
|||
name.insert(format!("°{}", $name), 1);
|
||||
Ok(QueryReply::Conversion(self.show(
|
||||
&res, &bottom,
|
||||
name, Mpq::one(),
|
||||
name, Num::one(),
|
||||
10)))
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
|
||||
use std::collections::{BTreeMap, BinaryHeap};
|
||||
use std::rc::Rc;
|
||||
use number::{Number, Unit, Dim};
|
||||
use number::{Number, Num, Unit, Dim};
|
||||
use std::cmp;
|
||||
use gmp::mpq::Mpq;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Factors(pub usize, pub Vec<Rc<String>>);
|
||||
|
@ -34,7 +33,7 @@ pub fn fast_decompose(value: &Number, quantities: &BTreeMap<Unit, String>) -> Un
|
|||
continue 'outer
|
||||
}
|
||||
}
|
||||
let num = Number(Mpq::one(), unit.clone());
|
||||
let num = Number(Num::one(), unit.clone());
|
||||
for &i in [-1, 1, 2].into_iter() {
|
||||
let res = (value / &num.powi(i)).unwrap();
|
||||
let score = res.complexity_score();
|
||||
|
@ -46,7 +45,7 @@ pub fn fast_decompose(value: &Number, quantities: &BTreeMap<Unit, String>) -> Un
|
|||
}
|
||||
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;
|
||||
let mut res = (value / &Number(Num::one(), unit.clone()).powi(pow)).unwrap().1;
|
||||
res.insert(Dim::new(&**name), pow as i64);
|
||||
return res
|
||||
}
|
||||
|
@ -64,7 +63,7 @@ pub fn factorize(value: &Number, quantities: &BTreeMap<Unit, Rc<String>>)
|
|||
let mut candidates: BinaryHeap<Factors> = BinaryHeap::new();
|
||||
let value_score = value.complexity_score();
|
||||
for (unit, name) in quantities.iter().rev() {
|
||||
let res = (value / &Number(Mpq::one(), unit.clone())).unwrap();
|
||||
let res = (value / &Number(Num::one(), unit.clone())).unwrap();
|
||||
//if res.1.len() >= value.1.len() {
|
||||
let score = res.complexity_score();
|
||||
// we are not making the unit any simpler
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::str::Chars;
|
|||
use std::iter::Peekable;
|
||||
use std::rc::Rc;
|
||||
use ast::*;
|
||||
use gmp::mpq::Mpq;
|
||||
use number::Num;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
|
@ -184,7 +184,7 @@ fn parse_term(mut iter: &mut Iter) -> Expr {
|
|||
Token::Plus => Expr::Plus(Box::new(parse_term(iter))),
|
||||
Token::Dash => Expr::Neg(Box::new(parse_term(iter))),
|
||||
Token::Slash => Expr::Frac(
|
||||
Box::new(Expr::Const(Mpq::one())),
|
||||
Box::new(Expr::Const(Num::one())),
|
||||
Box::new(parse_term(iter))),
|
||||
Token::LPar => {
|
||||
let res = parse_expr(iter);
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use gmp::mpq::Mpq;
|
||||
use number::Dim;
|
||||
use number::{Dim, Num};
|
||||
use ast::{Expr, Def, Defs};
|
||||
use std::rc::Rc;
|
||||
use value::Value;
|
||||
|
@ -232,7 +231,7 @@ impl Context {
|
|||
},
|
||||
Def::Unit(ref expr) => match self.eval(expr) {
|
||||
Ok(Value::Number(v)) => {
|
||||
if v.0 == Mpq::one() && reverse.contains(&*name) {
|
||||
if v.0 == Num::one() && reverse.contains(&*name) {
|
||||
self.reverse.insert(v.1.clone(), name.clone());
|
||||
}
|
||||
self.definitions.insert(name.clone(), expr.clone());
|
||||
|
|
290
src/number.rs
290
src/number.rs
|
@ -11,9 +11,191 @@ use std::rc::Rc;
|
|||
use std::fmt;
|
||||
use std::borrow::Borrow;
|
||||
use context::Context;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub type Int = Mpz;
|
||||
|
||||
/// Number type.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Num {
|
||||
/// Arbitrary-precision rational fraction.
|
||||
Mpq(Mpq),
|
||||
/// Machine floats.
|
||||
Float(f64),
|
||||
// /// Machine ints.
|
||||
// Int(i64),
|
||||
}
|
||||
|
||||
enum NumParity {
|
||||
Mpq(Mpq, Mpq),
|
||||
Float(f64, f64)
|
||||
}
|
||||
|
||||
impl Num {
|
||||
pub fn one() -> Num {
|
||||
Num::Mpq(Mpq::one())
|
||||
//Num::Int(1)
|
||||
}
|
||||
|
||||
pub fn zero() -> Num {
|
||||
Num::Mpq(Mpq::zero())
|
||||
//Num::Int(0)
|
||||
}
|
||||
|
||||
pub fn abs(&self) -> Num {
|
||||
match *self {
|
||||
Num::Mpq(ref mpq) => Num::Mpq(mpq.abs()),
|
||||
Num::Float(f) => Num::Float(f.abs()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parity(&self, other: &Num) -> NumParity {
|
||||
match (self, other) {
|
||||
(&Num::Float(left), right) =>
|
||||
NumParity::Float(left, right.into()),
|
||||
(left, &Num::Float(right)) =>
|
||||
NumParity::Float(left.into(), right),
|
||||
(&Num::Mpq(ref left), &Num::Mpq(ref right)) =>
|
||||
NumParity::Mpq(left.clone(), right.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div_rem(&self, other: &Num) -> (Num, Num) {
|
||||
match self.parity(other) {
|
||||
NumParity::Mpq(left, right) => {
|
||||
let div = &left / &right;
|
||||
let floor = &div.get_num() / div.get_den();
|
||||
let rem = &left - &(&right * &Mpq::ratio(&floor, &Mpz::one()));
|
||||
(Num::Mpq(Mpq::ratio(&floor, &Mpz::one())), Num::Mpq(rem))
|
||||
},
|
||||
NumParity::Float(left, right) => {
|
||||
(Num::Float(left / right), Num::Float(left % right))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_rational(&self) -> (Int, Int) {
|
||||
match *self {
|
||||
Num::Mpq(ref mpq) => (mpq.get_num(), mpq.get_den()),
|
||||
Num::Float(mut x) => {
|
||||
let mut m = [
|
||||
[1, 0],
|
||||
[0, 1]
|
||||
];
|
||||
let maxden = 1_000_000;
|
||||
|
||||
// loop finding terms until denom gets too big
|
||||
loop {
|
||||
let ai = x as i64;
|
||||
if m[1][0] * ai + m[1][1] > maxden {
|
||||
break;
|
||||
}
|
||||
let mut t;
|
||||
t = m[0][0] * ai + m[0][1];
|
||||
m[0][1] = m[0][0];
|
||||
m[0][0] = t;
|
||||
t = m[1][0] * ai + m[1][1];
|
||||
m[1][1] = m[1][0];
|
||||
m[1][0] = t;
|
||||
if x == ai as f64 {
|
||||
break; // division by zero
|
||||
}
|
||||
x = 1.0/(x - ai as f64);
|
||||
if x as i64 > i64::max_value() / 2 {
|
||||
break; // representation failure
|
||||
}
|
||||
}
|
||||
|
||||
(Int::from(m[0][0]), Int::from(m[1][0]))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_int(&self) -> Option<i64> {
|
||||
match *self {
|
||||
Num::Mpq(ref mpq) => (&(mpq.get_num() / mpq.get_den())).into(),
|
||||
Num::Float(f) => if f.abs() < i64::max_value() as f64 {
|
||||
Some(f as i64)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_f64(&self) -> f64 {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mpq> for Num {
|
||||
fn from(mpq: Mpq) -> Num {
|
||||
Num::Mpq(mpq)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mpz> for Num {
|
||||
fn from(mpz: Mpz) -> Num {
|
||||
Num::Mpq(Mpq::ratio(&mpz, &Mpz::one()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Num {
|
||||
fn from(i: i64) -> Num {
|
||||
Num::from(Mpz::from(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<f64> for &'a Num {
|
||||
fn into(self) -> f64 {
|
||||
match *self {
|
||||
Num::Mpq(ref mpq) => mpq.clone().into(),
|
||||
Num::Float(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Num {
|
||||
fn partial_cmp(&self, other: &Num) -> Option<Ordering> {
|
||||
match self.parity(other) {
|
||||
NumParity::Mpq(left, right) => left.partial_cmp(&right),
|
||||
NumParity::Float(left, right) => left.partial_cmp(&right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! num_binop {
|
||||
($what:ident, $func:ident) => {
|
||||
impl<'a, 'b> $what<&'b Num> for &'a Num {
|
||||
type Output = Num;
|
||||
|
||||
fn $func(self, other: &'b Num) -> Num {
|
||||
match self.parity(other) {
|
||||
NumParity::Mpq(left, right) =>
|
||||
Num::Mpq(left.$func(&right)),
|
||||
NumParity::Float(left, right) =>
|
||||
Num::Float(left.$func(&right)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
num_binop!(Add, add);
|
||||
num_binop!(Sub, sub);
|
||||
num_binop!(Mul, mul);
|
||||
num_binop!(Div, div);
|
||||
|
||||
impl<'a> Neg for &'a Num {
|
||||
type Output = Num;
|
||||
|
||||
fn neg(self) -> Num {
|
||||
match *self {
|
||||
Num::Mpq(ref mpq) => Num::Mpq(-mpq),
|
||||
Num::Float(f) => Num::Float(-f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Number type
|
||||
pub type Num = Mpq;
|
||||
/// Alias for the primary representation of dimensionality.
|
||||
pub type Unit = BTreeMap<Dim, i64>;
|
||||
|
||||
|
@ -22,7 +204,7 @@ pub type Unit = BTreeMap<Dim, i64>;
|
|||
pub struct Dim(pub Rc<String>);
|
||||
|
||||
/// The basic representation of a number with a unit.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Number(pub Num, pub Unit);
|
||||
|
||||
impl Borrow<str> for Dim {
|
||||
|
@ -43,41 +225,48 @@ impl Dim {
|
|||
}
|
||||
}
|
||||
|
||||
fn one() -> Mpq {
|
||||
Mpq::one()
|
||||
}
|
||||
|
||||
fn zero() -> Mpq {
|
||||
Mpq::zero()
|
||||
}
|
||||
|
||||
pub fn pow(left: &Mpq, exp: i32) -> Mpq {
|
||||
pub fn pow(left: &Num, exp: i32) -> Num {
|
||||
if exp < 0 {
|
||||
one() / pow(left, -exp)
|
||||
&Num::one() / &pow(left, -exp)
|
||||
} else {
|
||||
let left = match *left {
|
||||
Num::Mpq(ref left) => left,
|
||||
Num::Float(f) => return Num::Float(f.powi(exp))
|
||||
};
|
||||
let num = left.get_num().pow(exp as u32);
|
||||
let den = left.get_den().pow(exp as u32);
|
||||
Mpq::ratio(&num, &den)
|
||||
Num::Mpq(Mpq::ratio(&num, &den))
|
||||
}
|
||||
}
|
||||
|
||||
fn root(left: &Mpq, n: i32) -> Mpq {
|
||||
fn root(left: &Num, n: i32) -> Num {
|
||||
if n < 0 {
|
||||
one() / root(left, -n)
|
||||
&Num::one() / &root(left, -n)
|
||||
} else {
|
||||
let left = match *left {
|
||||
Num::Mpq(ref left) => left,
|
||||
Num::Float(f) => return Num::Float(f.powf(1.0 / n as f64))
|
||||
};
|
||||
let num = left.get_num().root(n as u32);
|
||||
let den = left.get_den().root(n as u32);
|
||||
Mpq::ratio(&num, &den)
|
||||
Num::Mpq(Mpq::ratio(&num, &den))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(rational: &Mpq, base: u8) -> (bool, String) {
|
||||
pub fn to_string(rational: &Num, base: u8) -> (bool, String) {
|
||||
use std::char::from_digit;
|
||||
|
||||
let sign = *rational < Mpq::zero();
|
||||
let sign = *rational < Num::zero();
|
||||
let rational = rational.abs();
|
||||
let num = rational.get_num();
|
||||
let den = rational.get_den();
|
||||
let (num, den) = rational.to_rational();
|
||||
let rational = match rational {
|
||||
Num::Mpq(mpq) => mpq,
|
||||
Num::Float(f) => {
|
||||
let mut m = Mpq::one();
|
||||
m.set_d(f);
|
||||
m
|
||||
},
|
||||
};
|
||||
let intdigits = (&num / &den).size_in_base(base) as u32;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
@ -85,10 +274,10 @@ pub fn to_string(rational: &Mpq, base: u8) -> (bool, String) {
|
|||
buf.push('-');
|
||||
}
|
||||
let zero = Mpq::zero();
|
||||
let one = Mpz::one();
|
||||
let ten = Mpz::from(base as u64);
|
||||
let one = Int::one();
|
||||
let ten = Int::from(base as u64);
|
||||
let ten_mpq = Mpq::ratio(&ten, &one);
|
||||
let mut cursor = rational / Mpq::ratio(&ten.pow(intdigits), &one);
|
||||
let mut cursor = &rational / &Mpq::ratio(&ten.pow(intdigits), &one);
|
||||
let mut n = 0;
|
||||
let mut only_zeros = true;
|
||||
let mut zeros = 0;
|
||||
|
@ -331,15 +520,15 @@ impl fmt::Display for NumberParts {
|
|||
|
||||
impl Number {
|
||||
pub fn one() -> Number {
|
||||
Number(one(), Unit::new())
|
||||
Number(Num::one(), Unit::new())
|
||||
}
|
||||
|
||||
pub fn one_unit(unit: Dim) -> Number {
|
||||
Number::new_unit(one(), unit)
|
||||
Number::new_unit(Num::one(), unit)
|
||||
}
|
||||
|
||||
pub fn zero() -> Number {
|
||||
Number(zero(), Unit::new())
|
||||
Number(Num::zero(), Unit::new())
|
||||
}
|
||||
|
||||
/// Creates a dimensionless value.
|
||||
|
@ -354,7 +543,7 @@ impl Number {
|
|||
Number(num, map)
|
||||
}
|
||||
|
||||
pub fn from_parts(integer: &str, frac: Option<&str>, exp: Option<&str>) -> Result<Mpq, String> {
|
||||
pub fn from_parts(integer: &str, frac: Option<&str>, exp: Option<&str>) -> Result<Num, String> {
|
||||
use std::str::FromStr;
|
||||
|
||||
let num = Mpz::from_str_radix(integer, 10).unwrap();
|
||||
|
@ -382,12 +571,12 @@ impl Number {
|
|||
};
|
||||
let num = &Mpq::ratio(&num, &Mpz::one()) + &frac;
|
||||
let num = &num * &exp;
|
||||
Ok(num)
|
||||
Ok(Num::Mpq(num))
|
||||
}
|
||||
|
||||
/// Computes the reciprocal (1/x) of the value.
|
||||
pub fn invert(&self) -> Number {
|
||||
Number(&one() / &self.0,
|
||||
Number(&Num::one() / &self.0,
|
||||
self.1.iter()
|
||||
.map(|(k, &power)| (k.clone(), -power))
|
||||
.collect::<Unit>())
|
||||
|
@ -404,7 +593,7 @@ impl Number {
|
|||
/// Computes the nth root of a value iff all of its units have
|
||||
/// powers divisible by n.
|
||||
pub fn root(&self, exp: i32) -> Result<Number, String> {
|
||||
if self.0 < Mpq::zero() {
|
||||
if self.0 < Num::zero() {
|
||||
return Err(format!("Complex numbers are not implemented"))
|
||||
}
|
||||
let mut res = Unit::new();
|
||||
|
@ -425,11 +614,8 @@ impl Number {
|
|||
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();
|
||||
let (num, den) = exp.0.to_rational();
|
||||
let one = Int::one();
|
||||
if den == one {
|
||||
let exp: Option<i64> = (&num).into();
|
||||
Ok(self.powi(exp.unwrap() as i32))
|
||||
|
@ -442,14 +628,24 @@ impl Number {
|
|||
}
|
||||
|
||||
pub fn numeric_value(&self, base: u8) -> (Option<String>, Option<String>) {
|
||||
match to_string(&self.0, base) {
|
||||
(true, v) => (Some(v), None),
|
||||
(false, v) => if {self.0.get_den() > Mpz::from(1_000_000) ||
|
||||
self.0.get_num() > Mpz::from(1_000_000_000u64)} {
|
||||
(None, Some(v))
|
||||
} else {
|
||||
(Some(format!("{:?}", self.0)), Some(v))
|
||||
}
|
||||
match self.0 {
|
||||
Num::Mpq(ref mpq) => {
|
||||
let num = mpq.get_num();
|
||||
let den = mpq.get_den();
|
||||
|
||||
match to_string(&self.0, base) {
|
||||
(true, v) => (Some(v), None),
|
||||
(false, v) => if {den > Mpz::from(1_000_000) ||
|
||||
num > Mpz::from(1_000_000_000u64)} {
|
||||
(None, Some(v))
|
||||
} else {
|
||||
(Some(format!("{}/{}", num, den)), Some(v))
|
||||
}
|
||||
}
|
||||
},
|
||||
Num::Float(_f) => {
|
||||
(None, Some(to_string(&self.0, base).1))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,7 +672,7 @@ impl Number {
|
|||
let orig = unit.iter().next().unwrap();
|
||||
// kg special case
|
||||
let (val, orig) = if &**(orig.0).0 == "kg" || &**(orig.0).0 == "kilogram" {
|
||||
(&self.0 * &pow(&Mpq::ratio(&Mpz::from(1000), &Mpz::one()), (*orig.1) as i32),
|
||||
(&self.0 * &pow(&Num::from(1000), (*orig.1) as i32),
|
||||
(Dim::new("gram"), orig.1))
|
||||
} else {
|
||||
(self.0.clone(), (orig.0.clone(), orig.1))
|
||||
|
@ -487,7 +683,7 @@ impl Number {
|
|||
}
|
||||
let abs = val.abs();
|
||||
if { abs >= pow(&v.0, (*orig.1) as i32) &&
|
||||
abs < pow(&(&v.0 * &Mpq::ratio(&Mpz::from(1000), &Mpz::one())),
|
||||
abs < pow(&(&v.0 * &Num::from(1000)),
|
||||
(*orig.1) as i32) } {
|
||||
let res = &val / &pow(&v.0, (*orig.1) as i32);
|
||||
// tonne special case
|
||||
|
@ -641,7 +837,7 @@ impl<'a, 'b> Div<&'b Number> for &'a Number {
|
|||
type Output = Option<Number>;
|
||||
|
||||
fn div(self, other: &Number) -> Self::Output {
|
||||
if other.0 == zero() {
|
||||
if other.0 == Num::zero() {
|
||||
None
|
||||
} else {
|
||||
self * &other.invert()
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::iter::Peekable;
|
|||
use ast::*;
|
||||
use gmp::mpz::Mpz;
|
||||
use gmp::mpq::Mpq;
|
||||
use number::Num;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
|
@ -423,6 +424,25 @@ pub type Iter<'a> = Peekable<TokenIterator<'a>>;
|
|||
fn is_func(name: &str) -> bool {
|
||||
match name {
|
||||
"sqrt" => true,
|
||||
"exp" => true,
|
||||
"ln" => true,
|
||||
"log" => true,
|
||||
"log2" => true,
|
||||
"log10" => true,
|
||||
"hypot" => true,
|
||||
"sin" => true,
|
||||
"cos" => true,
|
||||
"tan" => true,
|
||||
"asin" => true,
|
||||
"acos" => true,
|
||||
"atan" => true,
|
||||
"atan2" => true,
|
||||
"sinh" => true,
|
||||
"cosh" => true,
|
||||
"tanh" => true,
|
||||
"asinh" => true,
|
||||
"acosh" => true,
|
||||
"atanh" => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
@ -493,16 +513,19 @@ fn parse_term(mut iter: &mut Iter) -> Expr {
|
|||
Token::Hex(num) =>
|
||||
Mpz::from_str_radix(&*num, 16)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|()| Expr::Error(format!("Failed to parse hex"))),
|
||||
Token::Oct(num) =>
|
||||
Mpz::from_str_radix(&*num, 8)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|()| Expr::Error(format!("Failed to parse octal"))),
|
||||
Token::Bin(num) =>
|
||||
Mpz::from_str_radix(&*num, 2)
|
||||
.map(|x| Mpq::ratio(&x, &Mpz::one()))
|
||||
.map(Num::Mpq)
|
||||
.map(Expr::Const)
|
||||
.unwrap_or_else(|()| Expr::Error(format!("Failed to parse binary"))),
|
||||
Token::Plus => Expr::Plus(Box::new(parse_term(iter))),
|
||||
|
|
Loading…
Reference in a new issue