Awesome
Ratio
This library allows you to use Rational numbers in Elixir, to enable exact calculations with all numbers big and small.
Ratio follows the Numeric behaviour from Numbers, and can therefore be used in combination with any data type that uses Numbers (such as Tensor and ComplexNum).
Using Ratio
Ratio
defines arithmetic and comparison operations to work with rational numbers.
Rational numbers can be created by using Ratio.new/2
,
or by calling mathematical operators where one of the two operands is already a rational number.
Shorthand infix construction operator
Since version 4.0, Ratio
no longer defines an infix operator to create rational numbers.
Instead, rational numbers are made using Ratio.new
,
and as the output from using an existing Ratio
struct with a mathematical operation.
If you do want to use an infix operator such as
<~>
(supported in all Elixir versions)
or <|>
(deprecated in Elixir v1.14, the default of older versions of the Ratio
library)
you can add the following one-liner to the module(s) in which you want to use it:
defdelegate numerator <~> denominator, to: Ratio, as: :new
Basic functionality
Rational numbers can be manipulated using the functions in the Ratio
module.
iex> Ratio.mult(Ratio.new(1, 3), Ratio.new(1, 2))
Ratio.new(1, 6)
iex> Ratio.div(Ratio.new(2, 3), Ratio.new(8, 5))
Ratio.new(5, 12)
iex> Ratio.pow(Ratio.new(2), 4)
Ratio.new(16, 1)
The Ratio module also contains:
- a guard-safe
is_rational/1
check. - a
compare/2
function for use with e.g.Enum.sort
. to_float/1
to (lossly) convert a rational into a float.
Inline Math Operators and Casting
Ratio interopts with the Numbers
library:
If you want to overload Elixir's builtin math operators,
you can add use Numbers, overload_operators: true
to your module.
This also allows you to pass in a rational number as one argument
and an integer, float or Decimal (if you have installed the Decimal
library),
which are then cast to rational numbers whenever necessary:
defmodule IDoAlotOfMathHere do
defdelegate numerator <~> denominator, to: Ratio, as: :new
use Numbers, overload_operators: true
def calculate(input) do
num = input <~> 2
result = num * 2 + (3 <~> 4) * 5.0
result / 2
end
end
iex> IDoAlotOfMathHere.calculate(42)
Ratio.new(183, 8)
Installation
The package can be installed from hex, by adding :ratio
to your list of dependencies in mix.exs
:
def deps do
[
{:ratio, "~> 4.0"}
]
end
Changelog
- 4.0.1 -
- Fix compiler warnings on Elixir v1.16 (c.f. #128). Thank you, @kidq330
- 4.0.0 -
- Remove infix operator
<|>
as its usage is deprecated in Elixir v1.14. This is a backwards-incompatible change. If you want to use the old syntax with the new version, adddefdelegate num <|> denom, to: Ratio, as: :new
to your module. Alternatively, you might want to use the not-deprecated<~>
operator for this instead. - Switch the
Inspect
implementation to use the formRatio.new(10, 20)
instead of10 <|> 20
, related to above. This is also a backwards-incompatible change. - Remove implementation of
String.Chars
, as the earlier implementation was not a (non-programmer) human-readable format. - Ensure that the right-hand-side operand of calls to
Ratio.{add, sub, mult, div}/2
is allowed to be an integer for ease of use and backwards compatibility. Thank you for noticing this problem, @kipcole9 ! (c.f. #111)
- Remove infix operator
- 3.0.2 -
- Fixes: A bug with
<|>
when the numerator was a rational and the denuminator an integer. (c.f. #104) Thank you, @varsill!
- Fixes: A bug with
- 3.0.1 -
- Fixes:
- Problem where
Ratio.ceil/1
would be off-by-one (c.f. #89). Thank you, @Hajto! - Problem where
Ratio.pow/2
would return an integer rather than a new Ratio.(c.f. #100). Thank you, @speeddragon!
- Problem where
- Fixes:
- 3.0.0 -
- All operators except
<|>
are removed from Ratio. Instead, the operators defined byNumbers
(whichRatio
depends on) can be used, by addinguse Numbers, overload_operators: true
to your modules. (c.f. #34) - All math-based functions expect and return
Ratio
structs (rather than also working on integers and returning integers sometimes if the output turned out to be a whole number). (c.f. #43) This makes the code more efficient and more clear for users.- Ratio structs representing whole numbers are no longer implicitly converted 'back' to integers, as this behaviour was confusing. (c.f. #28)
- If conversion to/from other number-like types is really desired,
use the automatic conversions provided by
Ratio.new
,<|>
or (a bit slower but more general) the math functions exposed byNumbers
. Ratio ships with implementations ofCoerce.defcoercion
for Integer -> Ratio, Float -> Ratio and Decimal -> Ratio.
is_rational?/1
is replaced with the guard-safeis_rational/1
(only exported on Erlang versions where:erlang.map_get/2
is available, i.e. >= OTP 21.0.) (c.f. #37)Float.ratio/1
is now used to convert floats intoRatio
structs, rather than maintaining a hand-written version of this logic. (c.f #46) Thank you, @marcinwasowicz !- A lot of property-based tests have been added to get some level of confidence of the correctness of the library's operations.
- All operators except
- 2.4.2 Uses
extra_applications
inmix.exs
to silence warnings in Elixir 1.11 and onwards. - 2.4.1 Fixes a bug in the decimal conversion implementation where certain decimals were not converted properly. Thank you, @iterateNZ!
- 2.4.0 Adds optional support for automatic conversion from Decimals. Thank you, @kipcole !
- 2.3.1 Removes spurious printing statement in
Rational.FloatConversion
that would output a line of text at compile-time. Fixes support for Numbers v5+ which was broken. - 2.3.0 Adds
trunc
andto_floor_error
functions. - 2.1.1 Fixes implementation of
floor
andceil
which was counter-intuitive for negative numbers (it now correctly rounds towards negative infinity).- Drops support for Elixir versions older than 1.4, because of use of
Integer.floor_div
. - First version to support new Erlang versions (20 and onward) that have native
floor
andceil
functions.
- Drops support for Elixir versions older than 1.4, because of use of
- 2.1.0 Adds optional overloaded comparison operators.
- 2.0.0 Breaking change: Brought
Ratio.compare/2
in line with Elixir's comparison function guideline, to return:lt | :eq | :gt
. (This used to be-1 | 0 | 1
). - 1.2.9 Improved documentation. (Thanks, @morontt!)
- 1.2.8 Adding
:numbers
to theapplications:
list, to ensure that no warnings are thrown when building releases on Elixir < 1.4.0. - 1.2.6, 1.2.7 Improving documentation.
- 1.2.5 added
ceil/1
andfloor/1
. - 1.2.4 Fixes Elixir 1.4 warnings in the
mix.exs
file. - 1.2.3 Upgraded version of the
Numbers
dependency to 2.0. - 1.2.2 Added default argument to
Ratio.new/2
, to follow the Numeric behaviour fully, and addedRatio.minus/1
as alias forRatio.negate/1
for the same reason. - 1.2.0 Changed name of
Ratio.mul/2
toRatio.mult/2
, to avoid ambiguety, and to allow incorporation withNumbers
. Deprecation Warning was added to usingRatio.mul/2
. - 1.1.1 Negative floats are now converted correctly.
- 1.1.0 Elixir 1.3 compliance (Statefree if/else/catch clauses, etc.)
- 1.0.0 Proper
__using__
macro, with more readable option names. Stable release. - 0.6.0 First public release
- 0.0.1 First features
Difference with the 'rational' library
Observant readers might notice that there also is a 'rational' library in Hex.pm. The design idea between that library vs. this one is a bit different: Ratio
hides the internal data representation as much as possible, and numbers are therefore only created using Ratio.new/2
. This has as mayor advantage that the internal representation is always correct and simplified.
The Ratio library also (optionally) overrides (by virtue of the Numbers
library) the built-in math operations +, -, *, /, div, abs
so they work with combinations of integers, floats and rationals.
Finally, Ratio follows the Numeric behaviour, which means that it can be used with any data types that follow Numbers.