Skip to content

Commit

Permalink
Add docstrings, amplify README
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed Sep 15, 2021
1 parent eaa6fb5 commit 9746a1c
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 1 deletion.
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,76 @@

[![Build Status](https://travis-ci.org/timholy/Ratios.jl.svg?branch=master)](https://travis-ci.org/timholy/Ratios.jl)

This package provides types similar to Julia's `Rational` type, which make some sacrifices but have better computational performance.
This package provides types similar to Julia's `Rational` type, which make some sacrifices but have better computational performance at the risk of greater risk of overflow.

Currently the only type provided is `SimpleRatio(num, den)` for two integers `num` and `den`.

Demo:

```julia
julia> x, y, z = SimpleRatio(1, 8), SimpleRatio(1, 4), SimpleRatio(2, 8)
(SimpleRatio{Int}(1, 8), SimpleRatio{Int}(1, 4), SimpleRatio{Int}(2, 8))

julia> x+y
SimpleRatio{Int}(12, 32)

julia> x+z
SimpleRatio{Int}(3, 8)
```

`y` and `z` both represent the rational number `1//4`, but when performing arithmetic with `x`
`z` is preferred because it has the same denominator and is less likely to overflow.

To detect overflow, [SaferIntegers.jl](https://github.com/JeffreySarnoff/SaferIntegers.jl) is recommended:

```julia
julia> using Ratios, SaferIntegers

julia> x, y = SimpleRatio{SafeInt8}(1, 20), SimpleRatio{SafeInt8}(1, 21)
(SimpleRatio{SafeInt8}(1, 20), SimpleRatio{SafeInt8}(1, 21))

julia> x + y
ERROR: OverflowError: 20 * 21 overflowed for type Int8
Stacktrace:
[...]
```

[FastRationals](https://github.com/JeffreySarnoff/FastRationals.jl) is another package with safety and performance characteristics that lies somewhere between `SimpleRatio` and `Rational`:

```julia
julia> @benchmark x + y setup=((x, y) = (SimpleRatio(rand(-20:20), rand(2:20)), SimpleRatio(rand(-20:20), rand(2:20))))
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min max): 1.727 ns 28.575 ns ┊ GC (min max): 0.00% 0.00%
Time (median): 1.739 ns ┊ GC (median): 0.00%
Time (mean ± σ): 1.753 ns ± 0.445 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

▂ ▃ ▃ ▃ ▄ ▆ ▇ █ ▆ ▅ ▅ ▇ ▄ ▁
▂▁▂▁▃▁▅▁█▁█▁█▁█▁█▁█▁█▁█▁█▁█▁█▁▁█▁█▁█▁█▁▆▁▃▁▃▁▃▁▃▁▃▁▃▁▃▁▃▁▂ ▄
1.73 ns Histogram: frequency by time 1.76 ns <

Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark x + y setup=((x, y) = (FastRational(rand(-20:20), rand(2:20)), FastRational(rand(-20:20), rand(2:20))))
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min max): 3.192 ns 89.167 ns ┊ GC (min max): 0.00% 0.00%
Time (median): 3.215 ns ┊ GC (median): 0.00%
Time (mean ± σ): 3.307 ns ± 1.820 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

▃█▆█▃▂
▄███████▅▄▃▃▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▁▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂ ▃
3.19 ns Histogram: frequency by time 3.45 ns <

Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark x + y setup=((x, y) = (Rational(rand(-20:20), rand(2:20)), Rational(rand(-20:20), rand(2:20))))
BenchmarkTools.Trial: 10000 samples with 996 evaluations.
Range (min max): 22.385 ns 81.269 ns ┊ GC (min max): 0.00% 0.00%
Time (median): 32.777 ns ┊ GC (median): 0.00%
Time (mean ± σ): 33.162 ns ± 4.743 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

▁ ▇ ▄▂ ▁▆▃ ▂█▃ ▁ ▇ ▁ ▁
▁▁▁▁▁▄▂▁▆▂▂█▄▃█▅▆█▇▇█████████████▅█▆▂▁█▇▁▂▁█▄▁▂▁▁▆▂▁▁▁▁▃▁▁▁ ▃
22.4 ns Histogram: frequency by time 45.8 ns <

Memory estimate: 0 bytes, allocs estimate: 0.
```
46 changes: 46 additions & 0 deletions src/Ratios.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
"""
`Ratios` provides `SimpleRatio`, a faster variant of `Rational`.
Speed comes at the cost of greater vulnerability to overflow.
API summary:
- `r = SimpleRatio(num, den)` is equivalent to `num // den`
- `common_denominator` standardizes a collection of `SimpleRatio`s to have the same denominator,
making some arithmetic operations among them less likely to overflow.
"""
module Ratios

import Base: convert, promote_rule, *, /, +, -, ^, ==, decompose
Expand All @@ -11,6 +21,31 @@ struct SimpleRatio{T<:Integer} <: Real
den::T
end

"""
SimpleRatio(num::Integer, den::Integer)
Construct the equivalent of the rational number `num // den`.
Operations with `SimpleRatio` are faster, but also more vulnerable to integer overflow,
than with `Rational`. Arithmetic with `SimpleRatio` does not perform any simplification of the resulting ratios.
The best defense against overflow is to use ratios with the same denominator: in such cases,
`+` and `-` will skip forming the product of denominators. See [`common_denominator`](@ref).
If overflow is a risk, consider constructing them using SaferIntegers.jl.
# Examples
```julia
julia> x, y, z = SimpleRatio(1, 8), SimpleRatio(1, 4), SimpleRatio(2, 8)
(SimpleRatio{$Int}(1, 8), SimpleRatio{$Int}(1, 4), SimpleRatio{$Int}(2, 8))
julia> x+y
SimpleRatio{$Int}(12, 32)
julia> x+z
SimpleRatio{$Int}(3, 8)
```
"""
SimpleRatio(num::Integer, den::Integer) = SimpleRatio(promote(num, den)...)

convert(::Type{BigFloat}, r::SimpleRatio{S}) where {S} = BigFloat(r.num)/r.den
Expand Down Expand Up @@ -63,6 +98,17 @@ end

decompose(x::SimpleRatio) = x.num, 0, x.den

"""
common_denominator(x::SimpleRatio, ys::SimpleRatio...)
Return the equivalent of `(x, ys...)` but using a common denominator.
This can be useful to avoid overflow.
!!! info
This function is quite slow. In performance-sensitive contexts where the
ratios are constructed with literal constants, it is better to ensure a common
denominator at the time of original construction.
"""
function common_denominator(x::SimpleRatio, ys::SimpleRatio...)
all(y -> y.den == x.den, ys) && return (x, ys...)
cd = gcd(x.den, map(y -> y.den, ys)...) # common divisor
Expand Down

0 comments on commit 9746a1c

Please sign in to comment.