Appearance
有理数(Rational)
分数を既約分数として保持し、四則演算や大小比較を誤差なく行うライブラリ。
long long で分子・分母を保持し、計算途中では __int128 を利用する。
特徴
| 項目 | 内容 |
|---|---|
| 用途 | 分数の厳密計算・比較 |
| 保持する型 | long long |
| 中間計算 | __int128 |
| 正規化 | 常に既約分数、分母は正 |
| 特殊値 | Inf、-Inf、NaN |
考え方
有理数を分子 num、分母 den で管理する。
cpp
Rational a(2, 4); // 1/2
Rational b(3, -6); // -1/2
Rational c(0, 5); // 0/1構築時や演算後に自動で約分する。
有限値は den > 0 に統一する。
実装
cpp
#pragma once
#include <bits/stdc++.h>
using namespace std;
struct Rational {
using ll = long long;
using i128 = __int128_t;
ll num = 0, den = 1; // 有限値は den>0、den=0 は ±Inf / NaN
static int sgn(ll x) { return (x > 0) - (x < 0); }
static i128 abs128(i128 x) { return x < 0 ? -x : x; }
static i128 gcd128(i128 a, i128 b) {
while (b) { i128 r = a % b; a = b; b = r; }
return a;
}
static pair<ll, ll> reduce(i128 n, i128 d) {
if (d == 0) return {(ll)((n > 0) - (n < 0)), 0};
if (n == 0) return {0, 1};
if (d < 0) n = -n, d = -d;
i128 g = gcd128(abs128(n), d);
n /= g; d /= g;
if (n < LLONG_MIN || n > LLONG_MAX || d > LLONG_MAX) throw overflow_error("Rational overflow");
return {(ll)n, (ll)d};
}
static Rational make(i128 n, i128 d) {
auto [a, b] = reduce(n, d);
Rational r;
r.num = a; r.den = b;
return r;
}
Rational(ll n = 0, ll d = 1) { tie(num, den) = reduce(n, d); }
static Rational NaN() { return Rational(0, 0); }
static Rational Inf(int sign = 1) { return Rational(sign < 0 ? -1 : 1, 0); }
bool is_nan() const { return den == 0 && num == 0; }
bool is_inf() const { return den == 0 && num != 0; }
int sign() const { return is_nan() ? 0 : sgn(num); }
double to_double() const {
if (is_nan()) return numeric_limits<double>::quiet_NaN();
if (is_inf()) return num > 0 ? numeric_limits<double>::infinity() : -numeric_limits<double>::infinity();
return (double)num / den;
}
explicit operator double() const { return to_double(); }
Rational operator+() const { return *this; }
Rational operator-() const { return is_nan() ? NaN() : make(-(i128)num, den); }
static Rational add_finite(const Rational& a, const Rational& b, int s) {
i128 g = gcd128(a.den, b.den);
i128 n = (i128)a.num * (b.den / g) + (i128)s * b.num * (a.den / g);
i128 d = (i128)(a.den / g) * b.den;
return make(n, d);
}
friend Rational operator+(const Rational& a, const Rational& b) {
if (a.is_nan() || b.is_nan()) return NaN();
if (a.is_inf() && b.is_inf()) return a.num == b.num ? a : NaN();
if (a.is_inf()) return a;
if (b.is_inf()) return b;
return add_finite(a, b, 1);
}
friend Rational operator-(const Rational& a, const Rational& b) {
if (a.is_nan() || b.is_nan()) return NaN();
if (a.is_inf() && b.is_inf()) return a.num == b.num ? NaN() : a;
if (a.is_inf()) return a;
if (b.is_inf()) return Inf(-b.num);
return add_finite(a, b, -1);
}
friend Rational operator*(const Rational& a, const Rational& b) {
if (a.is_nan() || b.is_nan()) return NaN();
if (a.is_inf() || b.is_inf()) {
if ((!a.is_inf() && a.num == 0) || (!b.is_inf() && b.num == 0)) return NaN();
return Inf(a.sign() * b.sign());
}
return make((i128)a.num * b.num, (i128)a.den * b.den);
}
friend Rational operator/(const Rational& a, const Rational& b) {
if (a.is_nan() || b.is_nan()) return NaN();
if (b.is_inf()) return a.is_inf() ? NaN() : Rational();
if (a.is_inf()) return b.num == 0 ? NaN() : Inf(a.sign() * b.sign());
if (b.num == 0) return a.num == 0 ? NaN() : Inf(a.sign());
return make((i128)a.num * b.den, (i128)a.den * b.num);
}
Rational& operator+=(const Rational& x) { return *this = *this + x; }
Rational& operator-=(const Rational& x) { return *this = *this - x; }
Rational& operator*=(const Rational& x) { return *this = *this * x; }
Rational& operator/=(const Rational& x) { return *this = *this / x; }
friend bool operator==(const Rational& a, const Rational& b) {
return !a.is_nan() && !b.is_nan() && a.num == b.num && a.den == b.den;
}
friend bool operator!=(const Rational& a, const Rational& b) { return !(a == b); }
friend bool operator<(const Rational& a, const Rational& b) {
if (a.is_nan() || b.is_nan()) return false;
if (a.is_inf() && b.is_inf()) return a.num < b.num;
if (a.is_inf()) return a.num < 0;
if (b.is_inf()) return b.num > 0;
return (i128)a.num * b.den < (i128)b.num * a.den;
}
friend bool operator>(const Rational& a, const Rational& b) { return b < a; }
friend bool operator<=(const Rational& a, const Rational& b) { return a < b || a == b; }
friend bool operator>=(const Rational& a, const Rational& b) { return b < a || a == b; }
friend ostream& operator<<(ostream& os, const Rational& r) {
if (r.is_nan()) return os << "NaN";
if (r.is_inf()) return os << (r.num > 0 ? "Inf" : "-Inf");
return os << r.num << '/' << r.den;
}
friend istream& operator>>(istream& is, Rational& r) {
string s;
if (!(is >> s)) return is;
try {
Rational v;
if (s == "NaN" || s == "nan") v = NaN();
else if (s == "Inf" || s == "+Inf" || s == "inf" || s == "+inf") v = Inf();
else if (s == "-Inf" || s == "-inf") v = Inf(-1);
else {
auto parse = [](const string& t) -> ll {
size_t used = 0;
ll x = stoll(t, &used);
if (used != t.size()) throw invalid_argument("invalid Rational");
return x;
};
size_t p = s.find('/');
v = p == string::npos ? Rational(parse(s)) : Rational(parse(s.substr(0, p)), parse(s.substr(p + 1)));
}
r = v;
} catch (const exception&) { is.setstate(ios::failbit); }
return is;
}
};使用例
cpp
Rational a(1, 2), b(1, 3);
cout << a + b << '\n'; // 5/6
cout << a - b << '\n'; // 1/6
cout << a * b << '\n'; // 1/6
cout << a / b << '\n'; // 3/2
cout << (a > b) << '\n'; // 1
cout << Rational(2, 4); // 1/2特殊値
分母が0の場合は特殊値として扱う。
| 値 | 内部表現 |
|---|---|
| +Inf | 1/0 |
| -Inf | -1/0 |
| NaN | 0/0 |
cpp
Rational::Inf(); // +Inf
Rational::Inf(-1); // -Inf
Rational::NaN(); // NaN性質判定
cpp
r.is_inf(); // 無限大か
r.is_nan(); // NaNか
r.sign(); // -1、0、1
r.to_double(); // doubleへ変換大小比較
分母が正に正規化されているため、有限値では交差積で比較できる。
a/b < c/d は
ad < cb
と同値。
計算途中では __int128 を使用している。
cpp
Rational a(1, 3), b(2, 5);
cout << (a < b); // 1注意点
- 計算結果は常に約分される。
long longに収まらない分数はoverflow_errorとなる。__int128を使っても、任意精度の分数を表せるわけではない。num・denを直接変更すると正規化が崩れるため、原則としてコンストラクタや演算子を使う。NaNは自分自身とも等しくない。NaNを含む値をそのままstd::sortしない。- 無限大・NaNの演算規則は、このライブラリで定義したもの。
- 大量の分数演算は整数演算より高コストになるため、必要な場合だけ使用する。
関連
- GCD・LCM
- 整数のオーバーフロー
- 精度誤差