Improve doc comments (#16902)

Add runnableExamples
Use `reduce` in `initRational` and `//`
Add static tests
This commit is contained in:
konsumlamm 2021-02-02 07:04:30 +01:00 • committed by GitHub
commit 15d6be52a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 208 additions and 168 deletions

View file

@ -8,56 +8,94 @@
# #
## This module implements rational numbers, consisting of a numerator `num` and ## This module implements rational numbers, consisting of a numerator and
## a denominator `den`, both of type int. The denominator can not be 0. ## a denominator. The denominator can not be 0.
import math runnableExamples:
import hashes let
r1 = 1 // 2
r2 = -3 // 4
doAssert r1 + r2 == -1 // 4
doAssert r1 - r2 == 5 // 4
doAssert r1 * r2 == -3 // 8
doAssert r1 / r2 == -2 // 3
import std/[math, hashes]
type Rational*[T] = object type Rational*[T] = object
## a rational number, consisting of a numerator and denominator ## A rational number, consisting of a numerator `num` and a denominator `den`.
num*, den*: T num*, den*: T
func reduce*[T: SomeInteger](x: var Rational[T]) =
## Reduce the rational number `x`, so that the numerator and denominator
## have no common divisors other than 1 (and -1).
## If `x` is 0, raises `DivByZeroDefect`.
##
## **Note:** This is called automatically by the various operations on rationals.
runnableExamples:
var r = Rational[int](num: 2, den: 4) # 1/2
reduce(r)
doAssert r.num == 1
doAssert r.den == 2
let common = gcd(x.num, x.den)
if x.den > 0:
x.num = x.num div common
x.den = x.den div common
elif x.den < 0:
x.num = -x.num div common
x.den = -x.den div common
else:
raise newException(DivByZeroDefect, "division by zero")
func initRational*[T: SomeInteger](num, den: T): Rational[T] = func initRational*[T: SomeInteger](num, den: T): Rational[T] =
## Create a new rational number. ## Create a new rational number with numerator `num` and denominator `den`.
assert(den != 0, "a denominator of zero value is invalid") ## `den` must not be 0.
##
## **Note:** `den != 0` is not checked when assertions are turned off.
assert(den != 0, "a denominator of zero is invalid")
result.num = num result.num = num
result.den = den result.den = den
reduce(result)
func `//`*[T](num, den: T): Rational[T] = initRational[T](num, den) func `//`*[T](num, den: T): Rational[T] =
## A friendlier version of `initRational`. Example usage: ## A friendlier version of `initRational <#initRational,T,T>`_.
## runnableExamples:
## .. code-block:: nim let x = 1 // 3 + 1 // 5
## var x = 1//3 + 1//5 doAssert x == 8 // 15
initRational[T](num, den)
func `$`*[T](x: Rational[T]): string = func `$`*[T](x: Rational[T]): string =
## Turn a rational number into a string. ## Turn a rational number into a string.
runnableExamples:
doAssert $(1 // 2) == "1/2"
result = $x.num & "/" & $x.den result = $x.num & "/" & $x.den
func toRational*[T: SomeInteger](x: T): Rational[T] = func toRational*[T: SomeInteger](x: T): Rational[T] =
## Convert some integer `x` to a rational number. ## Convert some integer `x` to a rational number.
runnableExamples:
doAssert toRational(42) == 42 // 1
result.num = x result.num = x
result.den = 1 result.den = 1
func toRational*(x: float, func toRational*(x: float,
n: int = high(int) shr (sizeof(int) div 2 * 8)): Rational[int] = n: int = high(int) shr (sizeof(int) div 2 * 8)): Rational[int] =
## Calculates the best rational numerator and denominator ## Calculates the best rational approximation of `x`,
## that approximates to `x`, where the denominator is ## where the denominator is smaller than `n`
## smaller than `n` (default is the largest possible ## (default is the largest possible `int` for maximal resolution).
## int to give maximum resolution).
## ##
## The algorithm is based on the theory of continued fractions. ## The algorithm is based on the theory of continued fractions.
##
## .. code-block:: Nim
## import math, rationals
## for i in 1..10:
## let t = (10 ^ (i+3)).int
## let x = toRational(PI, t)
## let newPI = x.num / x.den
## echo x, " ", newPI, " error: ", PI - newPI, " ", t
# David Eppstein / UC Irvine / 8 Aug 1993 # David Eppstein / UC Irvine / 8 Aug 1993
# With corrections from Arno Formella, May 2008 # With corrections from Arno Formella, May 2008
runnableExamples:
import std/math
doAssert almostEqual(PI.toRational.toFloat, PI)
var var
m11, m22 = 1 m11, m22 = 1
m12, m21 = 0 m12, m21 = 0
@ -75,26 +113,14 @@ func toRational*(x: float,
result = m11 // m21 result = m11 // m21
func toFloat*[T](x: Rational[T]): float = func toFloat*[T](x: Rational[T]): float =
## Convert a rational number `x` to a float. ## Convert a rational number `x` to a `float`.
x.num / x.den x.num / x.den
func toInt*[T](x: Rational[T]): int = func toInt*[T](x: Rational[T]): int =
## Convert a rational number `x` to an int. Conversion rounds towards 0 if ## Convert a rational number `x` to an `int`. Conversion rounds towards 0 if
## `x` does not contain an integer value. ## `x` does not contain an integer value.
x.num div x.den x.num div x.den
func reduce*[T: SomeInteger](x: var Rational[T]) =
## Reduce rational `x`.
let common = gcd(x.num, x.den)
if x.den > 0:
x.num = x.num div common
x.den = x.den div common
elif x.den < 0:
x.num = -x.num div common
x.den = -x.den div common
else:
raise newException(DivByZeroDefect, "division by zero")
func `+`*[T](x, y: Rational[T]): Rational[T] = func `+`*[T](x, y: Rational[T]): Rational[T] =
## Add two rational numbers. ## Add two rational numbers.
let common = lcm(x.den, y.den) let common = lcm(x.den, y.den)
@ -103,24 +129,24 @@ func `+` *[T](x, y: Rational[T]): Rational[T] =
reduce(result) reduce(result)
func `+`*[T](x: Rational[T], y: T): Rational[T] = func `+`*[T](x: Rational[T], y: T): Rational[T] =
## Add rational `x` to int `y`. ## Add the rational `x` to the int `y`.
result.num = x.num + y * x.den result.num = x.num + y * x.den
result.den = x.den result.den = x.den
func `+`*[T](x: T, y: Rational[T]): Rational[T] = func `+`*[T](x: T, y: Rational[T]): Rational[T] =
## Add int `x` to rational `y`. ## Add the int `x` to the rational `y`.
result.num = x * y.den + y.num result.num = x * y.den + y.num
result.den = y.den result.den = y.den
func `+=`*[T](x: var Rational[T], y: Rational[T]) = func `+=`*[T](x: var Rational[T], y: Rational[T]) =
## Add rational `y` to rational `x`. ## Add the rational `y` to the rational `x` in-place.
let common = lcm(x.den, y.den) let common = lcm(x.den, y.den)
x.num = common div x.den * x.num + common div y.den * y.num x.num = common div x.den * x.num + common div y.den * y.num
x.den = common x.den = common
reduce(x) reduce(x)
func `+=`*[T](x: var Rational[T], y: T) = func `+=`*[T](x: var Rational[T], y: T) =
## Add int `y` to rational `x`. ## Add the int `y` to the rational `x` in-place.
x.num += y * x.den x.num += y * x.den
func `-`*[T](x: Rational[T]): Rational[T] = func `-`*[T](x: Rational[T]): Rational[T] =
@ -136,24 +162,24 @@ func `-` *[T](x, y: Rational[T]): Rational[T] =
reduce(result) reduce(result)
func `-`*[T](x: Rational[T], y: T): Rational[T] = func `-`*[T](x: Rational[T], y: T): Rational[T] =
## Subtract int `y` from rational `x`. ## Subtract the int `y` from the rational `x`.
result.num = x.num - y * x.den result.num = x.num - y * x.den
result.den = x.den result.den = x.den
func `-`*[T](x: T, y: Rational[T]): Rational[T] = func `-`*[T](x: T, y: Rational[T]): Rational[T] =
## Subtract rational `y` from int `x`. ## Subtract the rational `y` from the int `x`.
result.num = x * y.den - y.num result.num = x * y.den - y.num
result.den = y.den result.den = y.den
func `-=`*[T](x: var Rational[T], y: Rational[T]) = func `-=`*[T](x: var Rational[T], y: Rational[T]) =
## Subtract rational `y` from rational `x`. ## Subtract the rational `y` from the rational `x` in-place.
let common = lcm(x.den, y.den) let common = lcm(x.den, y.den)
x.num = common div x.den * x.num - common div y.den * y.num x.num = common div x.den * x.num - common div y.den * y.num
x.den = common x.den = common
reduce(x) reduce(x)
func `-=`*[T](x: var Rational[T], y: T) = func `-=`*[T](x: var Rational[T], y: T) =
## Subtract int `y` from rational `x`. ## Subtract the int `y` from the rational `x` in-place.
x.num -= y * x.den x.num -= y * x.den
func `*`*[T](x, y: Rational[T]): Rational[T] = func `*`*[T](x, y: Rational[T]): Rational[T] =
@ -163,30 +189,31 @@ func `*` *[T](x, y: Rational[T]): Rational[T] =
reduce(result) reduce(result)
func `*`*[T](x: Rational[T], y: T): Rational[T] = func `*`*[T](x: Rational[T], y: T): Rational[T] =
## Multiply rational `x` with int `y`. ## Multiply the rational `x` with the int `y`.
result.num = x.num * y result.num = x.num * y
result.den = x.den result.den = x.den
reduce(result) reduce(result)
func `*`*[T](x: T, y: Rational[T]): Rational[T] = func `*`*[T](x: T, y: Rational[T]): Rational[T] =
## Multiply int `x` with rational `y`. ## Multiply the int `x` with the rational `y`.
result.num = x * y.num result.num = x * y.num
result.den = y.den result.den = y.den
reduce(result) reduce(result)
func `*=`*[T](x: var Rational[T], y: Rational[T]) = func `*=`*[T](x: var Rational[T], y: Rational[T]) =
## Multiply rationals `y` to `x`. ## Multiply the rational `x` by `y` in-place.
x.num *= y.num x.num *= y.num
x.den *= y.den x.den *= y.den
reduce(x) reduce(x)
func `*=`*[T](x: var Rational[T], y: T) = func `*=`*[T](x: var Rational[T], y: T) =
## Multiply int `y` to rational `x`. ## Multiply the rational `x` by the int `y` in-place.
x.num *= y x.num *= y
reduce(x) reduce(x)
func reciprocal*[T](x: Rational[T]): Rational[T] = func reciprocal*[T](x: Rational[T]): Rational[T] =
## Calculate the reciprocal of `x`. (1/x) ## Calculate the reciprocal of `x` (`1/x`).
## If `x` is 0, raises `DivByZeroDefect`.
if x.num > 0: if x.num > 0:
result.num = x.den result.num = x.den
result.den = x.num result.den = x.num
@ -197,48 +224,59 @@ func reciprocal*[T](x: Rational[T]): Rational[T] =
raise newException(DivByZeroDefect, "division by zero") raise newException(DivByZeroDefect, "division by zero")
func `/`*[T](x, y: Rational[T]): Rational[T] = func `/`*[T](x, y: Rational[T]): Rational[T] =
## Divide rationals `x` by `y`. ## Divide the rational `x` by the rational `y`.
result.num = x.num * y.den result.num = x.num * y.den
result.den = x.den * y.num result.den = x.den * y.num
reduce(result) reduce(result)
func `/`*[T](x: Rational[T], y: T): Rational[T] = func `/`*[T](x: Rational[T], y: T): Rational[T] =
## Divide rational `x` by int `y`. ## Divide the rational `x` by the int `y`.
result.num = x.num result.num = x.num
result.den = x.den * y result.den = x.den * y
reduce(result) reduce(result)
func `/`*[T](x: T, y: Rational[T]): Rational[T] = func `/`*[T](x: T, y: Rational[T]): Rational[T] =
## Divide int `x` by Rational `y`. ## Divide the int `x` by the rational `y`.
result.num = x * y.den result.num = x * y.den
result.den = y.num result.den = y.num
reduce(result) reduce(result)
func `/=`*[T](x: var Rational[T], y: Rational[T]) = func `/=`*[T](x: var Rational[T], y: Rational[T]) =
## Divide rationals `x` by `y` in place. ## Divide the rational `x` by the rational `y` in-place.
x.num *= y.den x.num *= y.den
x.den *= y.num x.den *= y.num
reduce(x) reduce(x)
func `/=`*[T](x: var Rational[T], y: T) = func `/=`*[T](x: var Rational[T], y: T) =
## Divide rational `x` by int `y` in place. ## Divide the rational `x` by the int `y` in-place.
x.den *= y x.den *= y
reduce(x) reduce(x)
func cmp*(x, y: Rational): int = func cmp*(x, y: Rational): int =
## Compares two rationals. ## Compares two rationals. Returns
## * a value less than zero, if `x < y`
## * a value greater than zero, if `x > y`
## * zero, if `x == y`
(x - y).num (x - y).num
func `<`*(x, y: Rational): bool = func `<`*(x, y: Rational): bool =
## Returns true if `x` is less than `y`.
(x - y).num < 0 (x - y).num < 0
func `<=`*(x, y: Rational): bool = func `<=`*(x, y: Rational): bool =
## Returns tue if `x` is less than or equal to `y`.
(x - y).num <= 0 (x - y).num <= 0
func `==`*(x, y: Rational): bool = func `==`*(x, y: Rational): bool =
## Compares two rationals for equality.
(x - y).num == 0 (x - y).num == 0
func abs*[T](x: Rational[T]): Rational[T] = func abs*[T](x: Rational[T]): Rational[T] =
## Returns the absolute value of `x`.
runnableExamples:
doAssert abs(1 // 2) == 1 // 2
doAssert abs(-1 // 2) == 1 // 2
result.num = abs x.num result.num = abs x.num
result.den = abs x.den result.den = abs x.den
@ -248,29 +286,29 @@ func `div`*[T: SomeInteger](x, y: Rational[T]): T =
func `mod`*[T: SomeInteger](x, y: Rational[T]): Rational[T] = func `mod`*[T: SomeInteger](x, y: Rational[T]): Rational[T] =
## Computes the rational modulo by truncated division (remainder). ## Computes the rational modulo by truncated division (remainder).
## This is same as ``x - (x div y) * y``. ## This is same as `x - (x div y) * y`.
result = ((x.num * y.den) mod (y.num * x.den)) // (x.den * y.den) result = ((x.num * y.den) mod (y.num * x.den)) // (x.den * y.den)
reduce(result) reduce(result)
func floorDiv*[T: SomeInteger](x, y: Rational[T]): T = func floorDiv*[T: SomeInteger](x, y: Rational[T]): T =
## Computes the rational floor division. ## Computes the rational floor division.
## ##
## Floor division is conceptually defined as ``floor(x / y)``. ## Floor division is conceptually defined as `floor(x / y)`.
## This is different from the ``div`` operator, which is defined ## This is different from the `div` operator, which is defined
## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` ## as `trunc(x / y)`. That is, `div` rounds towards 0 and `floorDiv`
## rounds down. ## rounds down.
floorDiv(x.num * y.den, y.num * x.den) floorDiv(x.num * y.den, y.num * x.den)
func floorMod*[T: SomeInteger](x, y: Rational[T]): Rational[T] = func floorMod*[T: SomeInteger](x, y: Rational[T]): Rational[T] =
## Computes the rational modulo by floor division (modulo). ## Computes the rational modulo by floor division (modulo).
## ##
## This is same as ``x - floorDiv(x, y) * y``. ## This is same as `x - floorDiv(x, y) * y`.
## This func behaves the same as the ``%`` operator in python. ## This func behaves the same as the `%` operator in Python.
result = floorMod(x.num * y.den, y.num * x.den) // (x.den * y.den) result = floorMod(x.num * y.den, y.num * x.den) // (x.den * y.den)
reduce(result) reduce(result)
func hash*[T](x: Rational[T]): Hash = func hash*[T](x: Rational[T]): Hash =
## Computes hash for rational `x` ## Computes the hash for the rational `x`.
# reduce first so that hash(x) == hash(y) for x == y # reduce first so that hash(x) == hash(y) for x == y
var copy = x var copy = x
reduce(copy) reduce(copy)

View file

@ -1,6 +1,6 @@
import rationals, math import std/[rationals, math]
template main() =
var var
z = Rational[int](num: 0, den: 1) z = Rational[int](num: 0, den: 1)
o = initRational(num = 1, den = 1) o = initRational(num = 1, den = 1)
@ -9,63 +9,62 @@ var
m1 = -1 // 1 m1 = -1 // 1
tt = 10 // 2 tt = 10 // 2
doAssert(a == a) doAssert a == a
doAssert( (a-a) == z) doAssert a - a == z
doAssert( (a+b) == o) doAssert a + b == o
doAssert( (a/b) == o) doAssert a / b == o
doAssert( (a*b) == 1 // 4) doAssert a * b == 1 // 4
doAssert( (3/a) == 6 // 1) doAssert 3 / a == 6 // 1
doAssert( (a/3) == 1 // 6) doAssert a / 3 == 1 // 6
doAssert(a*b == 1 // 4) doAssert tt * z == z
doAssert(tt*z == z) doAssert 10 * a == tt
doAssert(10*a == tt) doAssert a * 10 == tt
doAssert(a*10 == tt) doAssert tt / 10 == a
doAssert(tt/10 == a) doAssert a - m1 == 3 // 2
doAssert(a-m1 == 3 // 2) doAssert a + m1 == -1 // 2
doAssert(a+m1 == -1 // 2) doAssert m1 + tt == 16 // 4
doAssert(m1+tt == 16 // 4) doAssert m1 - tt == 6 // -1
doAssert(m1-tt == 6 // -1)
doAssert(z < o) doAssert z < o
doAssert(z <= o) doAssert z <= o
doAssert(z == z) doAssert z == z
doAssert(cmp(z, o) < 0) doAssert cmp(z, o) < 0
doAssert(cmp(o, z) > 0) doAssert cmp(o, z) > 0
doAssert(o == o) doAssert o == o
doAssert(o >= o) doAssert o >= o
doAssert(not(o > o)) doAssert not(o > o)
doAssert(cmp(o, o) == 0) doAssert cmp(o, o) == 0
doAssert(cmp(z, z) == 0) doAssert cmp(z, z) == 0
doAssert(hash(o) == hash(o)) doAssert hash(o) == hash(o)
doAssert(a == b) doAssert a == b
doAssert(a >= b) doAssert a >= b
doAssert(not(b > a)) doAssert not(b > a)
doAssert(cmp(a, b) == 0) doAssert cmp(a, b) == 0
doAssert(hash(a) == hash(b)) doAssert hash(a) == hash(b)
var x = 1 // 3 var x = 1 // 3
x *= 5 // 1 x *= 5 // 1
doAssert(x == 5//3) doAssert x == 5 // 3
x += 2 // 9 x += 2 // 9
doAssert(x == 17//9) doAssert x == 17 // 9
x -= 9 // 18 x -= 9 // 18
doAssert(x == 25//18) doAssert x == 25 // 18
x /= 1 // 2 x /= 1 // 2
doAssert(x == 50//18) doAssert x == 50 // 18
var y = 1 // 3 var y = 1 // 3
y *= 4 y *= 4
doAssert(y == 4//3) doAssert y == 4 // 3
y += 5 y += 5
doAssert(y == 19//3) doAssert y == 19 // 3
y -= 2 y -= 2
doAssert(y == 13//3) doAssert y == 13 // 3
y /= 9 y /= 9
doAssert(y == 13//27) doAssert y == 13 // 27
doAssert toRational(5) == 5 // 1 doAssert toRational(5) == 5 // 1
doAssert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7 doAssert abs(toFloat(y) - 0.4814814814814815) < 1.0e-7
@ -96,3 +95,6 @@ doAssert floorDiv(1//1, 3//10) == 3
doAssert floorDiv(-1 // 1, 3 // 10) == -4 doAssert floorDiv(-1 // 1, 3 // 10) == -4
doAssert floorMod(3 // 10, 1 // 1) == 3 // 10 doAssert floorMod(3 // 10, 1 // 1) == 3 // 10
doAssert floorMod(-3 // 10, 1 // 1) == 7 // 10 doAssert floorMod(-3 // 10, 1 // 1) == 7 // 10
static: main()
main()