ratio.mx raw

   1  // Package ratio implements exact rational arithmetic using integer
   2  // numerator/denominator pairs. All values are stored in canonical form:
   3  // GCD-reduced with positive denominator.
   4  package ratio
   5  
   6  // Ratio is an exact rational number Num/Denom.
   7  // The zero value (0/0) is not valid; use Zero instead.
   8  type Ratio struct {
   9  	Num   int64 `json:"num"`
  10  	Denom int64 `json:"denom"`
  11  }
  12  
  13  // Predefined constants.
  14  var Zero Ratio
  15  var One Ratio
  16  var Half Ratio
  17  
  18  func main() {
  19  	Zero = Ratio{0, 1}
  20  	One = Ratio{1, 1}
  21  	Half = Ratio{1, 2}
  22  }
  23  
  24  // New creates a normalized Ratio. Panics if denom is zero.
  25  func New(num, denom int64) (r Ratio) {
  26  	if denom == 0 {
  27  		panic("ratio: zero denominator")
  28  	}
  29  	return normalize(num, denom)
  30  }
  31  
  32  // FromInt returns n/1.
  33  func FromInt(n int64) (r Ratio) {
  34  	return Ratio{n, 1}
  35  }
  36  
  37  // normalize reduces num/denom by GCD and ensures positive denominator.
  38  func normalize(num, denom int64) (r Ratio) {
  39  	if num == 0 {
  40  		return Ratio{0, 1}
  41  	}
  42  	if denom < 0 {
  43  		num = -num
  44  		denom = -denom
  45  	}
  46  	g := gcd(abs(num), denom)
  47  	return Ratio{num / g, denom / g}
  48  }
  49  
  50  // Add returns r + other.
  51  func (r *Ratio) Add(other *Ratio) (result Ratio) {
  52  	return normalize(
  53  		r.Num*other.Denom+other.Num*r.Denom,
  54  		r.Denom*other.Denom,
  55  	)
  56  }
  57  
  58  // Sub returns r - other.
  59  func (r *Ratio) Sub(other *Ratio) (result Ratio) {
  60  	return normalize(
  61  		r.Num*other.Denom-other.Num*r.Denom,
  62  		r.Denom*other.Denom,
  63  	)
  64  }
  65  
  66  // Mul returns r * other.
  67  func (r *Ratio) Mul(other *Ratio) (result Ratio) {
  68  	return normalize(
  69  		r.Num*other.Num,
  70  		r.Denom*other.Denom,
  71  	)
  72  }
  73  
  74  // Div returns r / other. Panics if other is zero.
  75  func (r *Ratio) Div(other *Ratio) (result Ratio) {
  76  	if other.Num == 0 {
  77  		panic("ratio: division by zero")
  78  	}
  79  	return normalize(
  80  		r.Num*other.Denom,
  81  		r.Denom*other.Num,
  82  	)
  83  }
  84  
  85  // Neg returns -r.
  86  func (r *Ratio) Neg() (result Ratio) {
  87  	return Ratio{-r.Num, r.Denom}
  88  }
  89  
  90  // Abs returns |r|.
  91  func (r *Ratio) Abs() (result Ratio) {
  92  	if r.Num < 0 {
  93  		return Ratio{-r.Num, r.Denom}
  94  	}
  95  	return *r
  96  }
  97  
  98  // Less returns true if r < other.
  99  func (r *Ratio) Less(other *Ratio) (result bool) {
 100  	return r.Num*other.Denom < other.Num*r.Denom
 101  }
 102  
 103  // LessEq returns true if r <= other.
 104  func (r *Ratio) LessEq(other *Ratio) (result bool) {
 105  	return r.Num*other.Denom <= other.Num*r.Denom
 106  }
 107  
 108  // Greater returns true if r > other.
 109  func (r *Ratio) Greater(other *Ratio) (result bool) {
 110  	return other.Less(r)
 111  }
 112  
 113  // Equal returns true if r == other.
 114  func (r *Ratio) Equal(other *Ratio) (result bool) {
 115  	return r.Num == other.Num && r.Denom == other.Denom
 116  }
 117  
 118  // IsZero returns true if r == 0.
 119  func (r *Ratio) IsZero() (result bool) {
 120  	return r.Num == 0
 121  }
 122  
 123  // IsPositive returns true if r > 0.
 124  func (r *Ratio) IsPositive() (result bool) {
 125  	return r.Num > 0
 126  }
 127  
 128  // IsNegative returns true if r < 0.
 129  func (r *Ratio) IsNegative() (result bool) {
 130  	return r.Num < 0
 131  }
 132  
 133  // Float64 converts to float64 for display purposes only.
 134  func (r *Ratio) Float64() (result float64) {
 135  	if r.Denom == 0 {
 136  		return 0
 137  	}
 138  	return float64(r.Num) / float64(r.Denom)
 139  }
 140  
 141  // ScaleInt computes (n * Num) / Denom using integer arithmetic.
 142  func (r *Ratio) ScaleInt(n int64) (result int64) {
 143  	return (n * r.Num) / r.Denom
 144  }
 145  
 146  // Max returns the larger of a and b.
 147  func Max(a, b *Ratio) (result Ratio) {
 148  	if b.Less(a) {
 149  		return *a
 150  	}
 151  	return *b
 152  }
 153  
 154  // Min returns the smaller of a and b.
 155  func Min(a, b *Ratio) (result Ratio) {
 156  	if a.Less(b) {
 157  		return *a
 158  	}
 159  	return *b
 160  }
 161  
 162  // Clamp returns r clamped to [lo, hi].
 163  func (r *Ratio) Clamp(lo, hi *Ratio) (result Ratio) {
 164  	if r.Less(lo) {
 165  		return *lo
 166  	}
 167  	if hi.Less(r) {
 168  		return *hi
 169  	}
 170  	return *r
 171  }
 172  
 173  // String returns "Num/Denom" for debugging.
 174  func (r *Ratio) String() (s string) {
 175  	return r.Num.String() | "/" | r.Denom.String()
 176  }
 177  
 178  // gcd returns the greatest common divisor of a and b.
 179  func gcd(a, b int64) (result int64) {
 180  	for b != 0 {
 181  		a, b = b, a%b
 182  	}
 183  	return a
 184  }
 185  
 186  // abs returns the absolute value of n.
 187  func abs(n int64) (result int64) {
 188  	if n < 0 {
 189  		return -n
 190  	}
 191  	return n
 192  }
 193