代数 - 有理数
有理数 \(\frac{n}{m} \in \mathbb Q\) を i64
の2つ組 Ratio(n, m)
で表す. Ratio::new(n, m)
は約分を行うがその分コストがあるので注意.
/// Algebra - Ratio Numbers
use crate::agroup; // IGNORE
use crate::algebra::field::*;
use crate::algebra::group_additive::*;
use crate::algebra::monoid::*;
use crate::algebra::ring::*;
use crate::monoid; // IGNORE
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Ratio(i64, i64); // Normalized (numerator / denominator)
impl Ratio {
pub fn new(a: i64, b: i64) -> Self {
Ratio(a, b).normalize()
}
fn normalize(&mut self) -> Self {
let g = Self::gcd(self.0.abs(), self.1.abs());
self.0 /= g;
self.1 /= g;
if self.1 < 0 {
self.0 *= -1;
self.1 *= -1;
}
*self
}
pub fn from(x: i64) -> Self {
Ratio(x, 1)
}
pub fn inv(&self) -> Self {
if self.0 > 0 {
Ratio(self.1, self.0)
} else if self.0 < 0 {
Ratio(-self.1, -self.0)
} else {
Ratio(1, 0) //Infinity
}
}
fn gcd(a: i64, b: i64) -> i64 {
if b == 0 {
a
} else {
Self::gcd(b, a % b)
}
}
fn lcm(a: i64, b: i64) -> i64 {
a / Self::gcd(a, b) * b
}
}
agroup! {
Ratio;
zero = Ratio(0, 1);
add(self, other) = {
let num = Self::lcm(self.1, other.1);
Ratio::new(self.0 * num / self.1 + other.0 * num / other.1, num)
};
neg(self) = {
Ratio(-self.0, self.1)
};
}
monoid! {
Ratio;
one = Ratio(1, 1);
mul(self, other) = { Self::new(self.0 * other.0, self.1 * other.1) }
}
impl Ring for Ratio {}
impl std::ops::Add<i64> for Ratio {
type Output = Self;
fn add(self, z: i64) -> Self {
Ratio::new(self.0 + self.1 * z, self.1)
}
}
impl std::ops::AddAssign<i64> for Ratio {
fn add_assign(&mut self, z: i64) {
self.0 += self.1 * z;
self.normalize();
}
}
impl std::ops::Sub<i64> for Ratio {
type Output = Self;
fn sub(self, z: i64) -> Self {
self + (-z)
}
}
impl std::ops::SubAssign<i64> for Ratio {
fn sub_assign(&mut self, z: i64) {
*self += -z;
}
}
impl std::ops::Mul<i64> for Ratio {
type Output = Self;
fn mul(self, z: i64) -> Self {
Self::new(self.0 * z, self.1)
}
}
impl std::ops::MulAssign<i64> for Ratio {
fn mul_assign(&mut self, z: i64) {
self.0 *= z;
self.normalize();
}
}
impl std::ops::Div for Ratio {
type Output = Self;
fn div(self, other: Self) -> Self {
Self::new(self.0 * other.1, self.1 * other.0)
}
}
impl Field for Ratio {}
impl std::ops::Div<i64> for Ratio {
type Output = Self;
fn div(self, other: i64) -> Self {
Self::new(self.0, self.1 * other)
}
}
impl std::ops::DivAssign<i64> for Ratio {
fn div_assign(&mut self, z: i64) {
self.1 *= z;
self.normalize();
}
}
impl PartialOrd for Ratio {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let left = self.0 * other.1;
let right = other.0 * self.1;
left.partial_cmp(&right)
}
}
impl Ord for Ratio {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(&other).unwrap()
}
}
#[macro_export]
macro_rules! r {
($x:expr, $y:expr) => {
Ratio::new($x, $y)
};
}
#[cfg(test)]
mod test_ratio {
use crate::algebra::ratio::*;
#[test]
fn it_works() {
assert_eq!(r!(1, 2) + r!(1, 2), Ratio(1, 1));
assert_eq!(r!(1, 3) + r!(2, 3), Ratio(1, 1));
assert_eq!(r!(1, 3) + r!(1, 3) + r!(1, 3), Ratio(1, 1));
assert_eq!(r!(1, 3) + 2, r!(7, 3));
assert_eq!(r!(1, 3) - r!(1, 3), Ratio(0, 1));
assert_eq!(r!(1, 3) - r!(2, 3), Ratio(-1, 3));
assert_eq!(r!(1, 2) - r!(1, 3), r!(1, 6));
assert_eq!(r!(1, 3) - r!(0, 2), Ratio(1, 3));
assert_eq!(r!(1, 1) * r!(1, 1), Ratio(1, 1));
assert_eq!(r!(1, 3) * r!(2, 3), Ratio(2, 9));
assert_eq!(r!(2, 3) * -1, Ratio(-2, 3));
assert_eq!(r!(2, 3) / -1, Ratio(-2, 3));
assert_eq!(r!(2, 3) / r!(2, 1), r!(1, 3));
assert_eq!(
Ratio::zero() * 2 + Ratio::one() / Ratio::from(2),
Ratio(1, 2)
);
}
#[test]
fn test_inv() {
assert_eq!(r!(1, 1).inv(), r!(1, 1));
assert_eq!(r!(1, 2).inv(), r!(2, 1));
assert_eq!(r!(1, -2).inv(), r!(-2, 1));
// 1/0 = Inf
assert_eq!(r!(0, 1).inv(), r!(1, 0));
assert_eq!(r!(0, 2).inv(), r!(1, 0));
assert_eq!(r!(0, -3).inv(), r!(1, 0));
// 1/Inf = 0
assert_eq!(r!(1, 0).inv(), r!(0, 1));
}
#[test]
fn test_zero() {
assert_eq!(r!(0, 1), Ratio(0, 1));
assert_eq!(r!(0, 2), Ratio(0, 1));
assert_eq!(r!(0, -3), Ratio(0, 1));
}
#[test]
fn test_mut() {
let mut r = r!(1, 2);
r *= 2;
assert_eq!(r, Ratio(1, 1));
r -= 1;
assert_eq!(r, Ratio(0, 1));
r += r!(1, 3);
assert_eq!(r, Ratio(1, 3));
r -= r!(1, 6);
assert_eq!(r, Ratio(1, 6));
r = r.inv();
assert_eq!(r, Ratio(6, 1));
}
}