///
/// 偏角クラス
/// (1,0) を 最小として、反時計回りに大小を定義する
/// https://yukicoder.me/users/3692/ai_code/69
///
public readonly struct Angle : IComparable
{
public readonly long X;
public readonly long Y;
public Angle(long x, long y)
{
if (x == 0 && y == 0) throw new ArgumentException("Zero vector is not allowed.");
var g = Gcd(Math.Abs(x), Math.Abs(y));
x /= g;
y /= g;
X = x;
Y = y;
}
// 上半平面判定
private bool Half()
{
// y > 0 or (y == 0 and x > 0)
return (Y > 0) || (Y == 0 && X > 0);
}
// 外積
private static long Cross(in Angle a, in Angle b)
{
return a.X * b.Y - a.Y * b.X;
}
public int CompareTo(Angle other)
{
var h1 = Half();
var h2 = other.Half();
// 半平面が違うなら上半平面が先
if (h1 != h2)
return h1 ? -1 : 1;
// 同一半平面なら外積符号
var cross = Cross(this, other);
if (cross > 0) return -1; // this が反時計回り側 → 角度小
if (cross < 0) return 1;
return 0; // 同方向
}
public override bool Equals(object obj)
{
if (obj is not Angle other) return false;
return X == other.X && Y == other.Y;
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
public static bool operator ==(Angle a, Angle b) => a.Equals(b);
public static bool operator !=(Angle a, Angle b) => !a.Equals(b);
public static bool operator <(Angle a, Angle b) => a.CompareTo(b) < 0;
public static bool operator >(Angle a, Angle b) => a.CompareTo(b) > 0;
public static bool operator <=(Angle a, Angle b) => a.CompareTo(b) <= 0;
public static bool operator >=(Angle a, Angle b) => a.CompareTo(b) >= 0;
private static long Gcd(long a, long b)
{
while (b != 0)
{
var t = a % b;
a = b;
b = t;
}
return a;
}
public override string ToString()
{
return $"({X}, {Y})";
}
}