diff --git a/crates/erg_common/lib.rs b/crates/erg_common/lib.rs index 41ae32e36..c955f35a9 100644 --- a/crates/erg_common/lib.rs +++ b/crates/erg_common/lib.rs @@ -25,6 +25,7 @@ pub mod opcode311; pub mod pathutil; pub mod python_util; pub mod random; +pub mod ratio; pub mod serialize; pub mod set; pub mod shared; diff --git a/crates/erg_common/ratio.rs b/crates/erg_common/ratio.rs new file mode 100644 index 000000000..c7633ca28 --- /dev/null +++ b/crates/erg_common/ratio.rs @@ -0,0 +1,394 @@ +use std::cmp::Ordering::{self, Equal, Greater, Less}; +use std::fmt::Display; +use std::ops::Neg; +use std::ops::{Add, Div, Mul, Rem, Sub}; + +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub struct Ratio { + numer: i64, + denom: i64, +} + +impl Ratio { + pub const fn new(numer: i64, denom: i64) -> Self { + if numer == 0 { + return Self::zero(); + } + if denom == 0 { + panic!("Zero is an invalid denominator!"); + } + + let gcd = gcd(numer, denom); + Self { + numer: numer / gcd, + denom: denom / gcd, + } + } + + #[inline] + pub const fn zero() -> Self { + Self { numer: 0, denom: 1 } + } + + #[inline] + pub fn one() -> Self { + Self { numer: 1, denom: 1 } + } + + const EPSILON: f64 = 1e-10; + #[inline] + pub fn float_new(f: f64) -> Self { + let mut f = f; + let mut minus = false; + match f.partial_cmp(&0f64) { + Some(Equal) => return Self::zero(), + Some(Less) => { + minus = true; + f *= -1.0; + } + Some(_) => {} + None => panic!("Something went wrong: {f} cannot compare"), + } + let mut n: i64 = 1; + let mut d: i64 = 1; + let mut error: f64 = (f - n as f64 / d as f64).abs(); + while error > Self::EPSILON { + if f > n as f64 / d as f64 { + n += 1; + } else { + d += 1; + } + let new_error = (f - n as f64 / d as f64).abs(); + error = new_error; + } + if minus { + n *= -1; + } + Ratio::new(n, d) + } + + #[inline] + pub fn to_float(self) -> f64 { + self.numer as f64 / self.denom as f64 + } + + #[inline] + pub fn to_int(self) -> i64 { + self.numer / self.denom + } + + #[inline] + pub fn denom(&self) -> i64 { + self.denom + } + + #[inline] + pub fn numer(&self) -> i64 { + self.numer + } + + #[inline] + pub fn to_le_bytes(&self) -> Vec { + [self.numer.to_le_bytes(), self.denom.to_le_bytes()].concat() + } +} + +impl Neg for Ratio { + type Output = Self; + + #[inline] + fn neg(self) -> Self::Output { + Self::new(-self.numer, self.denom) + } +} + +impl Add for Ratio { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self::Output { + let lcm = lcm(self.denom, rhs.denom); + let denom = lcm; + let numer = self.numer * lcm / self.denom + rhs.numer * lcm / rhs.denom; + Self::new(numer, denom) + } +} + +impl Sub for Ratio { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + let lcm = lcm(self.denom, rhs.denom); + let denom = lcm; + let numer = self.numer * lcm / self.denom - rhs.numer * lcm / rhs.denom; + Self::new(numer, denom) + } +} + +impl Mul for Ratio { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + let ac = gcd(self.numer, rhs.denom); + let bd = gcd(rhs.numer, self.denom); + Self::new( + self.numer / ac * rhs.numer / bd, + self.denom / bd * rhs.denom / ac, + ) + } +} + +impl Div for Ratio { + type Output = Self; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + let ac = gcd(self.numer, rhs.numer); + let bd = gcd(self.denom, rhs.denom); + Self::new( + self.numer / ac * rhs.denom / bd, + self.denom / bd * rhs.numer / ac, + ) + } +} + +impl Rem for Ratio { + type Output = Self; + + #[inline] + fn rem(self, rhs: Self) -> Self::Output { + if self == rhs { + return Self::zero(); + } else if rhs == Self::one() { + return self; + } + let common_denom = gcd(self.denom, rhs.denom); + let numer = + (self.numer * (rhs.denom / common_denom)) % (rhs.numer * (self.denom / common_denom)); + let denom = self.denom * (rhs.denom / common_denom); + Self::new(numer, denom) + } +} + +impl PartialOrd for Ratio { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + if self.denom == other.denom { + return self.numer.partial_cmp(&other.numer); + } + let lcm = lcm(self.denom, other.denom); + let l = self.numer * lcm / self.denom; + let r = other.numer * lcm / other.denom; + l.partial_cmp(&r) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + matches!(self.partial_cmp(other), Some(Less)) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + matches!(self.partial_cmp(other), Some(Less | Equal)) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + matches!(self.partial_cmp(other), Some(Greater)) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + matches!(self.partial_cmp(other), Some(Greater | Equal)) + } +} + +impl Display for Ratio { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", self.numer, self.denom) + } +} + +const fn gcd(x: i64, y: i64) -> i64 { + if y > x { + return gcd(y, x); + } + let mut x = x; + let mut y = y; + while y != 0 { + let t = y; + y = x % y; + x = t; + } + x +} + +fn lcm(x: i64, y: i64) -> i64 { + if x == 0 || y == 0 { + return 0; + } + x * y / gcd(x, y) +} + +#[cfg(test)] +mod test { + use super::Ratio; + use crate::ratio::{gcd, lcm}; + + #[test] + fn test_gcd() { + assert_eq!(1, gcd(1, 1)); + assert_eq!(-1, gcd(-1, -1)); + + assert_eq!(1, gcd(1, 1)); + assert_eq!(-1, gcd(-5, -7)); + assert_eq!(3, gcd(111, 30)); + + assert_eq!(0, gcd(0, 0)); + assert_eq!(5, gcd(0, 5)); + assert_eq!(5, gcd(5, 0)); + assert_eq!(1, gcd(1, 1)); + assert_eq!(gcd(-1, 1), gcd(1, -1)); + assert_eq!(-1, gcd(-1, -1)); + + assert_eq!(i64::MAX, gcd(i64::MAX, i64::MAX)); + assert_eq!(i64::MIN, gcd(i64::MIN, i64::MIN)); + assert_eq!(gcd(i64::MIN, i64::MAX), gcd(i64::MAX, i64::MIN)); + assert_eq!(gcd(0, i64::MAX), gcd(i64::MAX, 0)); + assert_eq!(gcd(i64::MIN, 1), gcd(1, i64::MIN)); + + assert_eq!(6, gcd(54, 24)); + assert_eq!(-6, gcd(-54, 24)); + assert_eq!(6, gcd(54, -24)); + assert_eq!(-6, gcd(-54, -24)); + assert_eq!(600, gcd(239520000, 1293400200)); + } + + #[test] + fn test_lcm() { + assert_eq!(91, lcm(7, 13)); + assert_eq!(6, lcm(2, -3)); + assert_eq!(-6, lcm(-2, 3)); + assert_eq!(-6, lcm(-2, -3)); + assert_eq!(-65, lcm(-5, 13)); + assert_eq!(-91, lcm(-7, -13)); + assert_eq!(lcm(0, 7), lcm(7, 0)); + assert_eq!(0, lcm(0, 0)); + assert_eq!(-1, lcm(-1, -1)); + } + + #[test] + fn test_rational_add() { + let a = Ratio::new(2, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::new(12, 1), a + b); + let a = Ratio::new(1, 1); + let b = Ratio::new(1, -1); + assert_eq!(Ratio::zero(), a + b); + let a = Ratio::new(1, 1); + let b = Ratio::new(-1, 1); + assert_eq!(Ratio::zero(), a + b); + let a = Ratio::new(1, 1); + let b = Ratio::new(-1, i64::MAX); + assert_eq!(Ratio::new(i64::MAX - 1, i64::MAX), a + b); + } + + #[test] + fn test_rational_sub() { + let a = Ratio::new(2, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::new(-8, 1), a - b); + let a = Ratio::new(1, 1); + let b = Ratio::new(1, 1); + assert_eq!(Ratio::zero(), a - b); + let a = Ratio::new(i64::MAX, i64::MAX); + let b = Ratio::new(1, 1); + assert_eq!(Ratio::zero(), a - b); + } + + #[test] + fn test_rational_mul() { + let a = Ratio::new(2, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::new(20, 1), a * b); + let a = Ratio::new(0, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::zero(), a * b); + let a = Ratio::new(10, 1); + let b = Ratio::new(0, 1); + assert_eq!(Ratio::zero(), a * b); + let a = Ratio::new(3, 2); + let b = Ratio::new(2, 3); + assert_eq!(Ratio::new(1, 1), a * b); + let a = Ratio::new(i64::MAX, 2); + let b = Ratio::new(2, i64::MAX); + assert_eq!(Ratio::new(1, 1), a * b); + } + + #[test] + fn test_rational_div() { + let a = Ratio::new(2, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::new(1, 5), a / b); + let a = Ratio::new(2, 1); + let b = Ratio::new(2, 1); + assert_eq!(Ratio::new(1, 1), a / b); + let a = Ratio::new(80, 363); + let b = Ratio::new(2, 5); + assert_eq!(Ratio::new(200, 363), a / b); + let a = Ratio::new(i64::MAX, i64::MIN + 1); + let b = Ratio::new(i64::MAX, i64::MIN + 1); + assert_eq!(Ratio::new(1, 1), a / b); + } + + #[test] + fn test_rational_rem() { + let a = Ratio::new(i64::MAX, i64::MIN + 1); + let b = Ratio::new(i64::MAX, i64::MIN + 1); + assert_eq!(Ratio::zero(), a % b); + let a = Ratio::new(i64::MAX, 127); + let b = Ratio::new(i64::MAX, 7); + assert_eq!(Ratio::new(72624976668147841, 1), a % b); + + let a = Ratio::new(2, 1); + let b = Ratio::new(10, 1); + assert_eq!(Ratio::new(2, 1), a % b); + let a = Ratio::new(3, 2); + let b = Ratio::new(3, 2); + assert_eq!(Ratio::zero(), a % b); + let a = Ratio::new(5, 2); + let b = Ratio::new(5, 3); + assert_eq!(Ratio::new(5, 6), a % b); + let a = Ratio::new(5, 2); + let b = Ratio::new(5, 3); + assert_eq!(Ratio::new(5, 6), a % b); + let a = Ratio::new(7, 2); + let b = Ratio::new(2, 5); + assert_eq!(Ratio::new(3, 10), a % b); + } + + #[test] + fn test_ratio_compare() { + let a = Ratio::new(1, 2); + let b = Ratio::new(1, 3); + assert!(a > b); + assert!(a >= b); + assert!(a >= a); + assert!(b <= a); + assert!(b <= b); + assert!(b < a); + } + + #[test] + fn test_float_new() { + assert_eq!(Ratio::new(-1, 1), Ratio::float_new(-1.0)); + assert_eq!(Ratio::new(0, 1), Ratio::float_new(-0.0)); + assert_eq!(Ratio::new(3, 10), Ratio::float_new(0.3)); + assert_eq!(Ratio::new(1, 7), Ratio::float_new(0.142857142857143)); + assert_eq!(Ratio::new(1, 997), Ratio::float_new(0.00100300902708124)); + assert_eq!(Ratio::new(1, 100000), Ratio::float_new(1e-5)); + assert_eq!(Ratio::new(1, 5000000000), Ratio::float_new(1e-10)); + } +} diff --git a/crates/erg_compiler/codegen.rs b/crates/erg_compiler/codegen.rs index 73b384fad..63d04c058 100644 --- a/crates/erg_compiler/codegen.rs +++ b/crates/erg_compiler/codegen.rs @@ -3383,7 +3383,7 @@ impl PyCodeGenerator { let mut wrapped = true; if !self.cfg.no_std && expr.should_wrap() { match expr.ref_t().derefine() { - v @ (Bool | Nat | Int | Float | Str) => { + v @ (Bool | Nat | Int | Ratio | Float | Str) => { self.emit_push_null(); self.emit_load_name_instr(Identifier::public(&v.qual_name())); } diff --git a/crates/erg_compiler/context/initialize/classes.rs b/crates/erg_compiler/context/initialize/classes.rs index 4b6f49af7..497274760 100644 --- a/crates/erg_compiler/context/initialize/classes.rs +++ b/crates/erg_compiler/context/initialize/classes.rs @@ -355,11 +355,24 @@ impl Context { float.register_trait_methods(Float, float_show); /* Ratio */ - // TODO: Int, Nat, Boolの継承元をRatioにする(今はFloat) let mut ratio = Self::builtin_mono_class(RATIO, 2); ratio.register_superclass(Obj, &obj); ratio.register_builtin_py_impl(REAL, Ratio, Const, Visibility::BUILTIN_PUBLIC, Some(REAL)); ratio.register_builtin_py_impl(IMAG, Ratio, Const, Visibility::BUILTIN_PUBLIC, Some(IMAG)); + ratio.register_builtin_py_impl( + NUMERATOR, + Int, + Const, + Visibility::BUILTIN_PUBLIC, + Some(NUMERATOR), + ); + ratio.register_builtin_py_impl( + DENOMINATOR, + Int, + Const, + Visibility::BUILTIN_PUBLIC, + Some(DENOMINATOR), + ); ratio.register_trait(self, mono(NUM)).unwrap(); ratio.register_trait(self, mono(ORD)).unwrap(); let mut ratio_ord = Self::builtin_methods(Some(mono(ORD)), 2); @@ -370,6 +383,10 @@ impl Context { Visibility::BUILTIN_PUBLIC, ); ratio.register_trait_methods(Ratio, ratio_ord); + ratio.register_py_builtin(OP_GT, fn1_met(Ratio, Ratio, Bool), Some(OP_GT), 0); + ratio.register_py_builtin(OP_GE, fn1_met(Ratio, Ratio, Bool), Some(OP_GE), 0); + ratio.register_py_builtin(OP_LT, fn1_met(Ratio, Ratio, Bool), Some(OP_LT), 0); + ratio.register_py_builtin(OP_LE, fn1_met(Ratio, Ratio, Bool), Some(OP_LE), 0); let mut ratio_eq = Self::builtin_methods(Some(mono(EQ)), 2); ratio_eq.register_builtin_erg_impl( OP_EQ, @@ -387,6 +404,14 @@ impl Context { ); ratio.register_trait_methods(Ratio, ratio_hash); ratio.register_trait(self, mono(EQ_HASH)).unwrap(); + let mut ratio_to_float = Self::builtin_methods(Some(mono(TO_FLOAT)), 1); + ratio_to_float.register_builtin_erg_impl( + FUNDAMENTAL_FLOAT, + fn0_met(Ratio, Float), + Const, + Visibility::BUILTIN_PUBLIC, + ); + ratio.register_trait_methods(Ratio, ratio_to_float); let op_t = fn1_met(Ratio, Ratio, Ratio); let mut ratio_add = Self::builtin_methods(Some(poly(ADD, vec![ty_tp(Ratio)])), 2); ratio_add.register_builtin_erg_impl( @@ -429,11 +454,12 @@ impl Context { None, ValueObj::builtin_class(Ratio), ); + // power is not closed operation on Ratio (cast to Float) ratio_mul.register_builtin_const( POW_OUTPUT, Visibility::BUILTIN_PUBLIC, None, - ValueObj::builtin_class(Ratio), + ValueObj::builtin_class(Float), ); ratio.register_trait_methods(Ratio, ratio_mul); let mut ratio_div = Self::builtin_methods(Some(poly(DIV, vec![ty_tp(Ratio)])), 2); @@ -491,7 +517,7 @@ impl Context { /* Int */ let mut int = Self::builtin_mono_class(INT, 2); - int.register_superclass(Float, &float); // TODO: Float -> Ratio + int.register_superclass(Ratio, &ratio); int.register_trait(self, mono(NUM)).unwrap(); // class("Rational"), // class("Integral"), @@ -642,7 +668,7 @@ impl Context { let mut int_div = Self::builtin_methods(Some(poly(DIV, vec![ty_tp(Int)])), 2); int_div.register_builtin_erg_impl( OP_DIV, - fn1_met(Int, Int, Float), + fn1_met(Int, Int, Ratio), Const, Visibility::BUILTIN_PUBLIC, ); @@ -650,7 +676,7 @@ impl Context { OUTPUT, Visibility::BUILTIN_PUBLIC, None, - ValueObj::builtin_class(Float), + ValueObj::builtin_class(Ratio), ); int_div.register_builtin_const( MOD_OUTPUT, @@ -802,7 +828,7 @@ impl Context { let mut nat_div = Self::builtin_methods(Some(poly(DIV, vec![ty_tp(Nat)])), 2); nat_div.register_builtin_erg_impl( OP_DIV, - fn1_met(Nat, Nat, Float), + fn1_met(Nat, Nat, Ratio), Const, Visibility::BUILTIN_PUBLIC, ); @@ -810,7 +836,7 @@ impl Context { OUTPUT, Visibility::BUILTIN_PUBLIC, None, - ValueObj::builtin_class(Float), + ValueObj::builtin_class(Ratio), ); nat_div.register_builtin_const( MOD_OUTPUT, @@ -3279,7 +3305,7 @@ impl Context { /* Int! */ let mut int_mut = Self::builtin_mono_class(MUT_INT, 2); int_mut.register_superclass(Int, &int); - int_mut.register_superclass(mono(MUT_FLOAT), &float_mut); + int_mut.register_superclass(mono(MUT_RATIO), &ratio_mut); let t = pr_met(mono(MUT_INT), vec![], None, vec![kw(KW_I, Int)], NoneType); int_mut.register_builtin_py_impl( PROC_INC, diff --git a/crates/erg_compiler/context/initialize/mod.rs b/crates/erg_compiler/context/initialize/mod.rs index c7c3aa075..8ddc0aac7 100644 --- a/crates/erg_compiler/context/initialize/mod.rs +++ b/crates/erg_compiler/context/initialize/mod.rs @@ -147,6 +147,8 @@ const MUT_FLOAT: &str = "Float!"; const EPSILON: &str = "EPSILON"; const REAL: &str = "real"; const IMAG: &str = "imag"; +const NUMERATOR: &str = "numerator"; +const DENOMINATOR: &str = "denominator"; const FUNC_AS_INTEGER_RATIO: &str = "as_integer_ratio"; const FUNC_CONJUGATE: &str = "conjugate"; const FUNC_IS_INTEGER: &str = "is_integer"; diff --git a/crates/erg_compiler/lib/core.d/Ratio.d.er b/crates/erg_compiler/lib/core.d/Ratio.d.er new file mode 100644 index 000000000..624e7e8b7 --- /dev/null +++ b/crates/erg_compiler/lib/core.d/Ratio.d.er @@ -0,0 +1,43 @@ +'''english +A class representing a ratio. +''' +'''japanese +有理数を表すクラス。 +''' +.Ratio: ClassType +.Ratio. + Denominator: .Int + Numerator: .Int + ''' + Returns a tuple of the numerator and denominator. + ''' + '''japanese + 分母分子のタプルを返す。 + ''' + '''erg + r: Ratio = 1.2345 + assert r.as_integer_ratio() == (2469, 2000) + ''' + as_integer_ratio: (self: .Ratio) -> (.Int, .Int) + ''' + Predecessor of `self` (`self - 1`). + ''' + '''japanese + `self`の1つ前の値を返す。 + ''' + '''erg + r: Ratio = 1.2345 + assert r.pred() == 0.2344 + ''' + pred: (self: .Ratio) -> .Ratio + ''' + Successor of `self` (`self + 1`). + ''' + '''japanese + `self`の1つ後の値を返す。 + ''' + '''erg + r: Ratio = 1.2345 + assert r.succ() == 2.2346 + ''' + succ: (self: .Ratio) -> .Ratio diff --git a/crates/erg_compiler/lib/core/_erg_int.py b/crates/erg_compiler/lib/core/_erg_int.py index 0c9157eb4..09d47661e 100644 --- a/crates/erg_compiler/lib/core/_erg_int.py +++ b/crates/erg_compiler/lib/core/_erg_int.py @@ -1,6 +1,7 @@ from _erg_control import then__ from _erg_result import Error from _erg_type import MutType +from _erg_ratio import Ratio class Int(int): @@ -34,8 +35,8 @@ def __sub__(self, other): def __mul__(self, other): return then__(int.__mul__(self, other), Int) - def __div__(self, other): - return then__(int.__div__(self, other), Int) + def __truediv__(self, other): + return then__(Ratio((self, other)), Ratio) def __floordiv__(self, other): return then__(int.__floordiv__(self, other), Int) diff --git a/crates/erg_compiler/lib/core/_erg_ratio.py b/crates/erg_compiler/lib/core/_erg_ratio.py new file mode 100644 index 000000000..2624f1fa5 --- /dev/null +++ b/crates/erg_compiler/lib/core/_erg_ratio.py @@ -0,0 +1,205 @@ +from fractions import Fraction + +from _erg_control import then__ +from _erg_result import Error +from _erg_type import MutType +from _erg_float import FloatMut + + +class Ratio(Fraction): + FRAC_ZERO = Fraction(0) + + def __new__(cls, fraction): + if isinstance(fraction, (int, float, Fraction)): + return super().__new__(cls, fraction) + + numerator, denominator = fraction + if isinstance(numerator, (int, float, Fraction)) and isinstance( + denominator, (int, float, Fraction) + ): + if (numerator, denominator) == (cls.FRAC_ZERO, cls.FRAC_ZERO): + return super().__new__(cls, cls.FRAC_ZERO) + return super().__new__(cls, numerator, denominator) + else: + raise ValueError("This class only accepts the fraction") + + def try_new(numerator: int, denominator: int): + if isinstance(numerator, int) and isinstance(denominator, int): + return Ratio((numerator, denominator)) + else: + return Error("not an integer") + + def bit_count(self): + if hasattr(int, "bit_count"): + return int.bit_count(self) + else: + return bin(self).count("1") + + def succ(self): + return Ratio(self) + 1 + + def pred(self): + return Ratio(self) - 1 + + def mutate(self): + return RatioMut(self) + + def __repr__(self): + return f"Ratio({self.numerator}/{self.denominator})" + + def __str__(self): + return f"{self.numerator}/{self.denominator}" + + def __add__(self, other): + return then__(super().__add__(other), Ratio) + + def __sub__(self, other): + return then__(super().__sub__(other), Ratio) + + def __mul__(self, other): + return then__(super().__mul__(other), Ratio) + + def __mod__(self, other): + return then__(super().__mod__(other), Ratio) + + def __truediv__(self, other): + return then__(super().__truediv__(other), Ratio) + + def __floordiv__(self, other): + return then__(super().__floordiv__(other), Ratio) + + def __pow__(self, other): + return float(self).__pow__(float(other)) + + def __rpow__(self, other): + return float(self).__rpow__(float(other)) + + def __pos__(self): + return self + + def __neg__(self): + return then__(super().__neg__(), Ratio) + + def __float__(self): + return super().__float__() + + +class RatioMut(MutType): + value: Ratio + + def __init__(self, fraction): + self.value = Ratio(fraction) + + def __int__(self): + return self.value.__int__() + + def __float__(self): + return self.value.__float__() + + def __repr__(self): + return self.value.__repr__() + + def __hash__(self): + return self.value.__hash__() + + def __eq__(self, other): + if isinstance(other, MutType): + return self.value == other.value + else: + return self.value == other + + def __ne__(self, other): + if isinstance(other, MutType): + return self.value != other.value + else: + return self.value != other + + def __le__(self, other): + if isinstance(other, MutType): + return self.value <= other.value + else: + return self.value <= other + + def __ge__(self, other): + if isinstance(other, MutType): + return self.value >= other.value + else: + return self.value >= other + + def __lt__(self, other): + if isinstance(other, MutType): + return self.value < other.value + else: + return self.value < other + + def __gt__(self, other): + if isinstance(other, MutType): + return self.value > other.value + else: + return self.value > other + + def __add__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value + other.value) + else: + return RatioMut(self.value + other) + + def __sub__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value - other.value) + else: + return RatioMut(self.value - other) + + def __mul__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value * other.value) + else: + return RatioMut(self.value * other) + + def __mod__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value % other.value) + else: + return RatioMut(self.value % other) + + def __floordiv__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value // other.value) + else: + return RatioMut(self.value // other) + + def __truediv__(self, other): + if isinstance(other, MutType): + return RatioMut(self.value / other.value) + else: + return RatioMut(self.value / other) + + def __pow__(self, other): + if isinstance(other, MutType): + return FloatMut(self.value**other.value) + else: + return FloatMut(self.value**other) + + def __pos__(self): + return self + + def __neg__(self): + return RatioMut(-self.value) + + def update(self, f): + self.value = Ratio(f(self.value)) + + def inc(self, i=1): + self.value = Ratio(self.value + i) + + def dec(self, i=1): + self.value = Ratio(self.value - i) + + def succ(self): + return self.value.succ() + + def pred(self): + return self.value.pred() + + def copy(self): + return RatioMut(self.value) diff --git a/crates/erg_compiler/lib/core/_erg_std_prelude.py b/crates/erg_compiler/lib/core/_erg_std_prelude.py index 58befaa6e..99fcdb645 100644 --- a/crates/erg_compiler/lib/core/_erg_std_prelude.py +++ b/crates/erg_compiler/lib/core/_erg_std_prelude.py @@ -8,16 +8,31 @@ from _erg_int import Int, IntMut from _erg_mutate_operator import mutate_operator from _erg_nat import Nat, NatMut -from _erg_range import (ClosedRange, LeftOpenRange, OpenRange, Range, - RangeIterator, RightOpenRange) +from _erg_range import ( + ClosedRange, + LeftOpenRange, + OpenRange, + Range, + RangeIterator, + RightOpenRange, +) from _erg_result import Error, is_ok +from _erg_ratio import Ratio, RatioMut from _erg_set import Set from _erg_str import Str, StrMut from _erg_type import MutType as _MutType -from _erg_iterable import (iterable_map, iterable_filter, iterable_reduce, - iterable_nth, iterable_skip, iterable_all, - iterable_any, iterable_position, iterable_find, - iterable_chain) +from _erg_iterable import ( + iterable_map, + iterable_filter, + iterable_reduce, + iterable_nth, + iterable_skip, + iterable_all, + iterable_any, + iterable_position, + iterable_find, + iterable_chain, +) Record = tuple diff --git a/crates/erg_compiler/transpile.rs b/crates/erg_compiler/transpile.rs index 987a1c5d5..06c48e339 100644 --- a/crates/erg_compiler/transpile.rs +++ b/crates/erg_compiler/transpile.rs @@ -470,6 +470,8 @@ impl PyScriptGenerator { .replace("from _erg_nat import Nat", "") .replace("from _erg_int import IntMut", "") .replace("from _erg_int import Int", "") + .replace("from _erg_ratio import RatioMut", "") + .replace("from _erg_ratio import Ratio", "") .replace("from _erg_bool import BoolMut", "") .replace("from _erg_bool import Bool", "") .replace("from _erg_str import StrMut", "") @@ -666,6 +668,7 @@ impl PyScriptGenerator { ValueObj::Bool(_) | ValueObj::Int(_) | ValueObj::Nat(_) + | ValueObj::Ratio(_) | ValueObj::Str(_) | ValueObj::Float(_) ) { @@ -756,7 +759,7 @@ impl PyScriptGenerator { fn transpile_acc(&mut self, acc: Accessor) -> String { let mut prefix = "".to_string(); match acc.ref_t().derefine() { - v @ (Type::Bool | Type::Nat | Type::Int | Type::Float | Type::Str) => { + v @ (Type::Bool | Type::Nat | Type::Int | Type::Ratio | Type::Float | Type::Str) => { self.load_builtin_types_if_not(); prefix.push_str(&v.qual_name()); prefix.push('('); @@ -773,15 +776,15 @@ impl PyScriptGenerator { match acc { Accessor::Ident(ident) => { match &ident.inspect()[..] { - "Str" | "Bytes" | "Bool" | "Nat" | "Int" | "Float" | "List" | "Dict" - | "Set" | "Str!" | "Bytes!" | "Bool!" | "Nat!" | "Int!" | "Float!" - | "List!" => { + "Str" | "Bytes" | "Bool" | "Nat" | "Int" | "Ratio" | "Float" | "List" + | "Dict" | "Set" | "Str!" | "Bytes!" | "Bool!" | "Nat!" | "Int!" | "Ratio!" + | "Float!" | "List!" => { self.load_builtin_types_if_not(); } "if" | "if!" | "for!" | "while" | "discard" => { self.load_builtin_controls_if_not(); } - "int" | "nat" | "float" | "str" => { + "int" | "nat" | "ratio" | "float" | "str" => { self.load_convertors_if_not(); } _ => {} diff --git a/crates/erg_compiler/ty/value.rs b/crates/erg_compiler/ty/value.rs index fae3b4f87..e16bc9d1a 100644 --- a/crates/erg_compiler/ty/value.rs +++ b/crates/erg_compiler/ty/value.rs @@ -7,15 +7,15 @@ use std::hash::{Hash, Hasher}; use std::ops::Neg; use std::sync::Arc; -use erg_common::dict::Dict; use erg_common::error::{ErrorCore, ErrorKind, Location}; use erg_common::fresh::FRESH_GEN; use erg_common::io::Input; use erg_common::python_util::PythonVersion; +use erg_common::ratio::Ratio; use erg_common::serialize::*; use erg_common::set::Set; use erg_common::traits::LimitedDisplay; -use erg_common::{dict, fmt_iter, impl_display_from_debug, log, switch_lang}; +use erg_common::{dict, dict::Dict, fmt_iter, impl_display_from_debug, log, switch_lang}; use erg_common::{ArcArray, Str}; use erg_parser::ast::{ConstArgs, ConstExpr}; @@ -510,6 +510,7 @@ impl TypeObj { pub enum ValueObj { Int(i32), Nat(u64), + Ratio(Ratio), Float(f64), Str(Str), Bool(bool), @@ -553,6 +554,13 @@ impl fmt::Debug for ValueObj { write!(f, "{n}") } } + Self::Ratio(r) => { + if cfg!(feature = "debug") { + write!(f, "Ratio({r})") + } else { + write!(f, "{r}") + } + } Self::Float(fl) => { // In Rust, .0 is shown omitted. if fl.fract() < 1e-10 { @@ -744,6 +752,7 @@ impl Neg for ValueObj { match self { Self::Int(i) => Self::Int(-i), Self::Nat(n) => Self::Int(-(n as i32)), + Self::Ratio(r) => Self::Ratio(-r), Self::Float(fl) => Self::Float(-fl), Self::Inf => Self::NegInf, Self::NegInf => Self::Inf, @@ -758,7 +767,7 @@ impl Hash for ValueObj { match self { Self::Int(i) => i.hash(state), Self::Nat(n) => n.hash(state), - // TODO: + Self::Ratio(r) => r.hash(state), Self::Float(f) => f.to_bits().hash(state), Self::Str(s) => s.hash(state), Self::Bool(b) => b.hash(state), @@ -884,6 +893,7 @@ impl TryFrom<&ValueObj> for f64 { match val { ValueObj::Int(i) => Ok(*i as f64), ValueObj::Nat(n) => Ok(*n as f64), + ValueObj::Ratio(r) => Ok(r.to_float()), ValueObj::Float(f) => Ok(*f), ValueObj::Inf => Ok(f64::INFINITY), ValueObj::NegInf => Ok(f64::NEG_INFINITY), @@ -899,6 +909,7 @@ impl TryFrom<&ValueObj> for usize { match val { ValueObj::Int(i) => usize::try_from(*i).map_err(|_| ()), ValueObj::Nat(n) => usize::try_from(*n).map_err(|_| ()), + ValueObj::Ratio(r) => usize::try_from(r.to_int()).map_err(|_| ()), ValueObj::Float(f) => Ok(*f as usize), ValueObj::Bool(b) => Ok(if *b { 1 } else { 0 }), _ => Err(()), @@ -993,14 +1004,27 @@ impl ValueObj { pub const fn is_num(&self) -> bool { matches!( self, - Self::Float(_) | Self::Int(_) | Self::Nat(_) | Self::Bool(_) | Self::Inf | Self::NegInf + Self::Float(_) + | Self::Ratio(_) + | Self::Int(_) + | Self::Nat(_) + | Self::Bool(_) + | Self::Inf + | Self::NegInf ) } pub const fn is_float(&self) -> bool { matches!( self, - Self::Float(_) | Self::Int(_) | Self::Nat(_) | Self::Bool(_) + Self::Float(_) | Self::Ratio(_) | Self::Int(_) | Self::Nat(_) | Self::Bool(_) + ) + } + + pub const fn is_ratio(&self) -> bool { + matches!( + self, + Self::Ratio(_) | Self::Int(_) | Self::Nat(_) | Self::Bool(_) ) } @@ -1097,12 +1121,32 @@ impl ValueObj { .parse::() .ok() .map(Self::Float), - // TODO: - Type::Ratio => content - .replace('_', "") - .parse::() - .ok() - .map(Self::Float), + Type::Ratio => { + // eg: "1/2", "1_000.0/2_000.0" + if content.contains('/') { + let (num, den) = content.split_once('/').unwrap(); + let num = num + .replace('_', "") + .parse::() + .ok() + .map(Ratio::float_new); + let den = den + .replace('_', "") + .parse::() + .ok() + .map(Ratio::float_new); + match (num, den) { + (Some(num), Some(den)) => Some(Self::Ratio(num / den)), + _ => None, + } + } else { + content + .replace('_', "") + .parse::() + .ok() + .map(|f| Self::Ratio(Ratio::float_new(f))) + } + } Type::Str => { if &content[..] == "\"\"" { Some(Self::Str(Str::from(""))) @@ -1142,6 +1186,13 @@ impl ValueObj { (n as i32).to_le_bytes().to_vec(), ] .concat(), + Self::Ratio(r) => { + let num = r.numer(); + let den = r.denom(); + let tup = + ArcArray::from(vec![ValueObj::from(num as i32), ValueObj::from(den as i32)]); + tuple_into_bytes(&tup, python_ver) + } Self::Float(f) => [ vec![DataTypePrefix::BinFloat as u8], f.to_le_bytes().to_vec(), @@ -1192,6 +1243,7 @@ impl ValueObj { match self { Self::Int(_) => Type::Int, Self::Nat(_) => Type::Nat, + Self::Ratio(_) => Type::Ratio, Self::Float(_) => Type::Float, Self::Str(_) => Type::Str, Self::Bool(_) => Type::Bool, @@ -1235,6 +1287,7 @@ impl ValueObj { match self { Self::Int(i) => Some(*i), Self::Nat(n) => i32::try_from(*n).ok(), + Self::Ratio(r) => i32::try_from(r.to_int()).ok(), Self::Bool(b) => Some(if *b { 1 } else { 0 }), Self::Float(f) if f.round() == *f => Some(*f as i32), _ => None, @@ -1245,6 +1298,7 @@ impl ValueObj { match self { Self::Int(i) => Some(*i as f64), Self::Nat(n) => Some(*n as f64), + Self::Ratio(r) => Some(r.to_float()), Self::Bool(b) => Some(if *b { 1.0 } else { 0.0 }), Self::Float(f) => Some(*f), _ => None, @@ -1311,15 +1365,22 @@ impl ValueObj { // REVIEW: allow_divergenceオプションを付けるべきか? pub fn try_add(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Int(l + r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::Nat(l + r)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l + r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l + r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::Int(l as i32 + r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l - r as f64)), - (Self::Int(l), Self::Float(r)) => Some(Self::Float(l as f64 - r)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) + r)), (Self::Nat(l), Self::Float(r)) => Some(Self::Float(l as f64 - r)), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l + r)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l + r as i32)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) + r)), + (Self::Int(l), Self::Float(r)) => Some(Self::Float(l as f64 - r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Ratio(l + Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Ratio(l + Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Ratio(l + r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::Float(l.to_float() + r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l - r as f64)), (Self::Float(l), Self::Int(r)) => Some(Self::Float(l - r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::Float(l - r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l + r)), (Self::Str(l), Self::Str(r)) => Some(Self::Str(Str::from(format!("{l}{r}")))), (Self::List(l), Self::List(r)) => { let lis = Arc::from([l, r].concat()); @@ -1335,15 +1396,22 @@ impl ValueObj { pub fn try_sub(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Int(l - r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::Int(l as i32 - r as i32)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l - r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l - r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from(l as i32 - r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l - r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) - r)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 - r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l - r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l - r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l - r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) - r)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 - r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Ratio(l - Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Ratio(l - Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Ratio(l - r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() - r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l - r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l - r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l - r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l - r)), (inf @ (Self::Inf | Self::NegInf), other) | (other, inf @ (Self::Inf | Self::NegInf)) if other != Self::Inf && other != Self::NegInf => @@ -1356,15 +1424,22 @@ impl ValueObj { pub fn try_mul(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l * r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::Nat(l * r)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l * r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l * r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::Int(l as i32 * r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l * r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Int(l as i32 * r.to_int() as i32)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 * r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l * r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l * r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l * r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(l * r.to_int() as i32)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 * r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Ratio(l * Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Ratio(l * Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Ratio(l * r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::Ratio(l * Ratio::float_new(r))), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l * r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l * r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l * r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l * r)), (Self::Str(l), Self::Nat(r)) => Some(Self::Str(Str::from(l.repeat(r as usize)))), (inf @ (Self::Inf | Self::NegInf), _) | (_, inf @ (Self::Inf | Self::NegInf)) => { Some(inf) @@ -1375,15 +1450,24 @@ impl ValueObj { pub fn try_div(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Float(l as f64 / r as f64)), - (Self::Nat(l), Self::Nat(r)) => Some(Self::Float(l as f64 / r as f64)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l / r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::Float(l as f64 / r as f64)), - (Self::Nat(l), Self::Int(r)) => Some(Self::Float(l as f64 / r as f64)), + (Self::Nat(l), Self::Nat(r)) => Some(Self::Ratio(Ratio::new(l as i64, r as i64))), + (Self::Nat(l), Self::Int(r)) => Some(Self::Ratio(Ratio::new(l as i64, r as i64))), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) / r)), + (Self::Nat(l), Self::Float(r)) => Some(Self::Float(l as f64 / r)), + (Self::Int(l), Self::Nat(r)) => Some(Self::Ratio( + Ratio::new(l as i64, 1) / Ratio::new(r as i64, 1), + )), + (Self::Int(l), Self::Int(r)) => Some(Self::Ratio(Ratio::new(l as i64, r as i64))), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) / r)), + (Self::Int(l), Self::Float(r)) => Some(Self::Float(l as f64 / r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Ratio(l / Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Ratio(l / Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Ratio(l / r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::Float(l.to_float() / r)), (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l / r as f64)), - (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 / r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l / r as f64)), - (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 / r)), + (Self::Float(l), Self::Int(r)) => Some(Self::Float(l / r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::Float(l / r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l / r)), // TODO: x/±Inf = 0 _ => None, } @@ -1391,15 +1475,22 @@ impl ValueObj { pub fn try_floordiv(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Int(l / r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::Nat(l / r)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float((l / r).floor())), - (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l / r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::Int(l as i32 / r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::Float((l / r as f64).floor())), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Int(l as i32 / r.to_int() as i32)), (Self::Nat(l), Self::Float(r)) => Some(Self::Float((l as f64 / r).floor())), - (Self::Float(l), Self::Int(r)) => Some(Self::Float((l / r as f64).floor())), + (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l / r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l / r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Int(l / r.to_int() as i32)), (Self::Int(l), Self::Float(r)) => Some(Self::Float((l as f64 / r).floor())), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Int(l.to_int() as i32 / r as i32)), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Int(l.to_int() as i32 / r)), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Int((l.to_int() / r.to_int()) as i32)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::Float((l.to_float() / r).floor())), + (Self::Float(l), Self::Nat(r)) => Some(Self::Float((l / r as f64).floor())), + (Self::Float(l), Self::Int(r)) => Some(Self::Float((l / r as f64).floor())), + (Self::Float(l), Self::Ratio(r)) => Some(Self::Float((l / r.to_float()).floor())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float((l / r).floor())), // TODO: x//±Inf = 0 _ => None, } @@ -1407,45 +1498,65 @@ impl ValueObj { pub fn try_pow(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Int(l.pow(r.try_into().ok()?))), (Self::Nat(l), Self::Nat(r)) => Some(Self::Nat(l.pow(r.try_into().ok()?))), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l.powf(r))), - (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l.pow(r.try_into().ok()?))), (Self::Nat(l), Self::Int(r)) => Some(Self::Nat(l.pow(r.try_into().ok()?))), - (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l.powf(r as f64))), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Float((l as f64).powf(r.to_float()))), (Self::Nat(l), Self::Float(r)) => Some(Self::Float((l as f64).powf(r))), - (Self::Float(l), Self::Int(r)) => Some(Self::Float(l.powi(r))), + (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l.pow(r.try_into().ok()?))), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l.pow(r.try_into().ok()?))), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Int(l.pow(r.to_int() as u32))), (Self::Int(l), Self::Float(r)) => Some(Self::Float((l as f64).powf(r))), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Float((l.to_float()).powf(r as f64))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Float((l.to_float()).powf(r as f64))), + (Self::Ratio(l), Self::Ratio(r)) => { + Some(Self::Float((l.to_float()).powf(r.to_float()))) + } + (Self::Ratio(l), Self::Float(r)) => Some(Self::Float((l.to_float()).powf(r))), + (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l.powf(r as f64))), + (Self::Float(l), Self::Int(r)) => Some(Self::Float(l.powi(r))), + (Self::Float(l), Self::Ratio(r)) => Some(Self::Float(l.powf(r.to_float()))), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l.powf(r))), _ => None, } } pub fn try_mod(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::Int(l % r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::Nat(l % r)), - (Self::Float(l), Self::Float(r)) => Some(Self::Float(l % r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l % r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::Int(l as i32 % r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l % r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) % r)), (Self::Nat(l), Self::Float(r)) => Some(Self::Float(l as f64 % r)), - (Self::Float(l), Self::Int(r)) => Some(Self::Float(l % r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::Int(l % r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::Int(l % r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::Ratio(Ratio::new(l as i64, 1) % r)), (Self::Int(l), Self::Float(r)) => Some(Self::Float(l as f64 % r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::Ratio(l % Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::Ratio(l % Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::Ratio(l % r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::Float(l.to_float() % r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::Float(l % r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::Float(l % r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::Float(l % r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::Float(l % r)), _ => None, } } pub fn try_gt(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l > r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l > r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l > r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l > r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from(l as i32 > r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l > r as f64)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 > r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l > r as f64)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l > r)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l > r as i32)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 > r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l > Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l > Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l > r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() > r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l > r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l > r as f64)), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l > r)), (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(false)), (Self::Inf, Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_)) | (Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_), Self::NegInf) => { @@ -1461,15 +1572,22 @@ impl ValueObj { pub fn try_ge(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l >= r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l >= r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l >= r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l >= r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from(l as i32 >= r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l >= r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) >= r)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 >= r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l >= r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l >= r as i32)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 >= r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) >= r)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l >= r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l >= Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l >= Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l >= r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() >= r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l >= r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l >= r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l >= r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l >= r)), (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(true)), (Self::Inf, Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_)) | (Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_), Self::NegInf) => { @@ -1485,15 +1603,22 @@ impl ValueObj { pub fn try_lt(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l < r)), + (Self::Nat(l), Self::Int(r)) => Some(Self::from((l as i32) < r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l < r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l < r)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) < r)), + (Self::Nat(l), Self::Float(r)) => Some(Self::from((l as f64) < r)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l < r)), (Self::Int(l), Self::Nat(r)) => Some(Self::from(l < r as i32)), - (Self::Nat(l), Self::Int(r)) => Some(Self::from((l as i32) < r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) < r)), + (Self::Int(l), Self::Float(r)) => Some(Self::from((l as f64) < r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l < Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l < Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l < r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() < r)), (Self::Float(l), Self::Nat(r)) => Some(Self::from(l < r as f64)), - (Self::Nat(l), Self::Float(r)) => Some(Self::from((l as f64) < r)), (Self::Float(l), Self::Int(r)) => Some(Self::from(l < r as f64)), - (Self::Int(l), Self::Float(r)) => Some(Self::from((l as f64) < r)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l < r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l < r)), (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(false)), (Self::Inf, Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_)) | (Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_), Self::NegInf) => { @@ -1509,15 +1634,22 @@ impl ValueObj { pub fn try_le(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l <= r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l <= r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l <= r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l <= r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from((l as i32) <= r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l <= r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) <= r)), (Self::Nat(l), Self::Float(r)) => Some(Self::from((l as f64) <= r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l <= r as f64)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l <= r)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l <= r as i32)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) <= r)), (Self::Int(l), Self::Float(r)) => Some(Self::from((l as f64) <= r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l <= Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l <= Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l <= r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() <= r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l <= r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l <= r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l <= r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l <= r)), (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(true)), (Self::Inf, Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_)) | (Self::Nat(_) | Self::Int(_) | Self::Float(_) | Self::Bool(_), Self::NegInf) => { @@ -1533,19 +1665,26 @@ impl ValueObj { pub fn try_eq(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l == r)), + (Self::Bool(l), Self::Bool(r)) => Some(Self::from(l == r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l == r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l == r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l == r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from(l as i32 == r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l == r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) == r)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 == r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l == r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l == r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l == r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) == r)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 == r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l == Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l == Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l == r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() == r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l == r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l == r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l == r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l == r)), + (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(true)), (Self::Str(l), Self::Str(r)) => Some(Self::from(l == r)), - (Self::Bool(l), Self::Bool(r)) => Some(Self::from(l == r)), (Self::Type(l), Self::Type(r)) => Some(Self::from(l == r)), - (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(true)), // TODO: _ => None, } @@ -1553,19 +1692,26 @@ impl ValueObj { pub fn try_ne(self, other: Self) -> Option { match (self, other) { - (Self::Int(l), Self::Int(r)) => Some(Self::from(l != r)), + (Self::Bool(l), Self::Bool(r)) => Some(Self::from(l != r)), (Self::Nat(l), Self::Nat(r)) => Some(Self::from(l != r)), - (Self::Float(l), Self::Float(r)) => Some(Self::from(l != r)), - (Self::Int(l), Self::Nat(r)) => Some(Self::from(l != r as i32)), (Self::Nat(l), Self::Int(r)) => Some(Self::from(l as i32 != r)), - (Self::Float(l), Self::Nat(r)) => Some(Self::from(l != r as f64)), + (Self::Nat(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) != r)), (Self::Nat(l), Self::Float(r)) => Some(Self::from(l as f64 != r)), - (Self::Float(l), Self::Int(r)) => Some(Self::from(l != r as f64)), + (Self::Int(l), Self::Nat(r)) => Some(Self::from(l != r as i32)), + (Self::Int(l), Self::Int(r)) => Some(Self::from(l != r)), + (Self::Int(l), Self::Ratio(r)) => Some(Self::from(Ratio::new(l as i64, 1) != r)), (Self::Int(l), Self::Float(r)) => Some(Self::from(l as f64 != r)), + (Self::Ratio(l), Self::Nat(r)) => Some(Self::from(l != Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Int(r)) => Some(Self::from(l != Ratio::new(r as i64, 1))), + (Self::Ratio(l), Self::Ratio(r)) => Some(Self::from(l != r)), + (Self::Ratio(l), Self::Float(r)) => Some(Self::from(l.to_float() == r)), + (Self::Float(l), Self::Nat(r)) => Some(Self::from(l != r as f64)), + (Self::Float(l), Self::Int(r)) => Some(Self::from(l != r as f64)), + (Self::Float(l), Self::Ratio(r)) => Some(Self::from(l != r.to_float())), + (Self::Float(l), Self::Float(r)) => Some(Self::from(l != r)), + (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(false)), (Self::Str(l), Self::Str(r)) => Some(Self::from(l != r)), - (Self::Bool(l), Self::Bool(r)) => Some(Self::from(l != r)), (Self::Type(l), Self::Type(r)) => Some(Self::from(l != r)), - (Self::Inf, Self::Inf) | (Self::NegInf, Self::NegInf) => Some(Self::Bool(false)), _ => None, } } diff --git a/tests/eval/build_in_function.rs b/tests/eval/build_in_function.rs index 8c6085f6a..a18d6e14e 100644 --- a/tests/eval/build_in_function.rs +++ b/tests/eval/build_in_function.rs @@ -50,7 +50,7 @@ fn eval_interpolation_1() { #[test] #[ignore] fn eval_interpolation_2() { - assert_eq!(eval("print! \"\\{0.005}\""), successful_output("0.005\n")); + assert_eq!(eval("print! \"\\{0.005}\""), successful_output("1/200\n")); } #[test] diff --git a/tests/eval/literal.rs b/tests/eval/literal.rs index dcbde94ad..a15d2feb7 100644 --- a/tests/eval/literal.rs +++ b/tests/eval/literal.rs @@ -156,5 +156,7 @@ fn eval_assert_inequality_2() { #[test] #[ignore] fn eval_ratio() { - assert_eq!(eval("print! 0.1234"), successful_output("0.1234\n")); + assert_eq!(eval("print! 3/10"), successful_output("3/10\n")); + assert_eq!(eval("print! 2.3%2"), successful_output("3/10\n")); + assert_eq!(eval("print! 0.3"), successful_output("3/10\n")); } diff --git a/tests/should_ok/ratio.er b/tests/should_ok/ratio.er new file mode 100644 index 000000000..48415aed0 --- /dev/null +++ b/tests/should_ok/ratio.er @@ -0,0 +1,27 @@ + +r: Ratio = 7.5 # 15/2 +# basic operations +assert r == 15/2 +assert r + 1 == 17/2 +assert r - 1 == 13/2 +assert r * 2 == 15 +assert r / 2 == 15/4 +assert r // 2 == 3 +assert r % 2 == 3/2 + +r_zero: Ratio = 0.0 # 0/1 +# zero operations +assert r_zero + 1 == 1 +assert r_zero - 1 == -1 +assert r_zero * 2 == 0 +assert r_zero / 1 == 0 + + +# comparison +assert r < 7.5001 +assert r <= 7.5 +assert r >= 7.5 +assert r > 7.49999 +r_small1: Ratio = 1/100000000 +r_small2: Ratio = 1/1000000001 +assert r_small1 > r_small2 diff --git a/tests/should_ok/sym_op.er b/tests/should_ok/sym_op.er index 2b682eb6c..f7e0c4d7c 100644 --- a/tests/should_ok/sym_op.er +++ b/tests/should_ok/sym_op.er @@ -2,4 +2,4 @@ perform f, x, y = f x, y assert 3 == perform `_+_`, 1, 2 assert -1 == perform `_-_`, 1, 2 assert 2 == perform `*`, 1, 2 -assert 0.5 - perform(`/`, 1, 2) < Float.EPSILON +assert 0.5 - perform(`/`, 1, 2) == 0.0 diff --git a/tests/test.rs b/tests/test.rs index 514b14149..0cedde031 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -232,6 +232,11 @@ fn exec_int() -> Result<(), ()> { expect_success("tests/should_ok/int.er", 0) } +#[test] +fn exec_ratio() -> Result<(), ()> { + expect_success("tests/should_ok/ratio.er", 0) +} + #[test] fn exec_interpolation() -> Result<(), ()> { expect_success("tests/should_ok/interpolation.er", 0) @@ -650,7 +655,7 @@ fn exec_pyimport_err() -> Result<(), ()> { #[test] fn exec_set() -> Result<(), ()> { - expect_failure("examples/set.er", 3, 1) + expect_success("examples/set.er", 3) } #[test]