Skip to content

Commit

Permalink
Improved tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wiebemarten committed Mar 30, 2016
1 parent 9d30956 commit b2745fe
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 24 deletions.
65 changes: 59 additions & 6 deletions README.md
@@ -1,6 +1,49 @@
# Math

The missing Math module for Elixir.
The Math module adds many useful functions that extend Elixir's standard library.

- General Functions
- `a <~> b` Comparison of floats, to check if they are _nearly_ equal.
- `Math.pow(x, n)` Arithmetic exponentiation. Works both with integer powers and floats.
- `Math.sqrt(x)` The square root of *x*.
- `Math.nth_root(x, n)` The n-th root of *x*.
- `Math.isqrt(x)` The integer square root of *x*.
- `Math.gcd(a, b)` The greatest common divisor of *a* and *b*.
- `Math.lcm(a, b)` The least common multiple of *a* and *b*.
- `Math.factorial(n)` The *n*-th factorial number.
- `Math.k_permutations(n, k)` The number of distinct ways to create groups of size *k* from *n* distinct elements.
- `Math.k_combinations(n, k)` The number of distinct ways to create groups of size *k* from *n* distinct elements where order does not matter.


- Logarithms
- `Math.exp(x)` Calculates ℯ to the xth power.
- `Math.log(x)` Calculates the natural logarithm (base ``) of *x*.
- `Math.log(x, b)` Calculates the base-*b* logarithm of *x*
- `Math.log2(x)` Calculates the binary logarithm (base `2`) of *x*.
- `Math.log10(x)` Calculates the common logarithm (base `10`) of *x*.
- `Math.e` Returns a floating-point approximation of the number ℯ.

- Trigonometry
- `Math.pi` Returns a floating-point approximation of the number *π*.
- `Math.deg2rad(x)` converts from degrees to radians.
- `Math.rad2deg(x)` converts from radians to degrees.
- `Math.sin(x)` The sine of *x*.
- `Math.cos(x)` The cosine of *x*.
- `Math.tan(x)` The tangent of *x*.
- `Math.asin(x)` The inverse sine of *x*.
- `Math.acos(x)` The inverse cosine of *x*.
- `Math.atan(x)` The inverse tangent of *x*.
- `Math.atan2(x, y)` The inverse tangent of *x* and *y*. This variant returns the inverse tangent in the correct quadrant, as the signs of both *x* and *y* are known.
- `Math.sinh(x)` The hyperbolic sine of *x*.
- `Math.cosh(x)` The hyperbolic cosine of *x*.
- `Math.tanh(x)` The hyperbolic tangent of *x*.
- `Math.asinh(x)` The inverse hyperbolic sine of *x*.
- `Math.acosh(x)` The inverse hyperbolic cosine of *x*.
- `Math.atanh(x)` The inverse hyperbolic tangent of *x*.
- Working with Collections
- `Math.Enum.product(collection)` The result of multiplying all elements in the passed collection.
- `Math.Enum.mean(collection)` the mean of the numbers in the collection.
- `Math.Enum.median(collection)` the median of the numbers in the collection.

## Installation

Expand All @@ -9,12 +52,22 @@ Math is [available in Hex](https://hex.pm/packages/math). The package can be ins
1. Add math to your list of dependencies in `mix.exs`:

def deps do
[{:math, "~> 0.0.1"}]
[
{:math, "~> 0.1.0"}
]
end

2. Ensure math is started before your application:
2. Require or import the Math library anywhere in your code you'd like:

def application do
[applications: [:math]]
end
require Math

or

import Math

(Importing allows usage of the `<~>` operator)

## Changelog
- 0.2.0 Added `factorial/1`, `nth_sqrt/2`, `k_permutations/2`, `k_combinations/2`, `gcd/2`, `lcm/2` and `Math.Enum` functions. Improved documentation.
- 0.1.0 Added integer variant of `pow/1`, `isqrt/2`, `deg2rad/1`, `rad2deg/1`. Improved documentation.
- 0.0.1 First implementation, mostly a wrapper around Erlang's `:math` library.
85 changes: 74 additions & 11 deletions lib/math.ex
Expand Up @@ -75,7 +75,7 @@ defmodule Math do
# General

@doc """
Arithmetic exponentiation. Returns *x* to the *n* -th power.
Arithmetic exponentiation. Calculates *x* to the *n* -th power.
When both *x* and *n* are integers and *n* is positive, returns an `integer`.
When *n* is a negative integer, returns a `float`.
Expand Down Expand Up @@ -119,13 +119,13 @@ defmodule Math do
defp _pow(x, n, y), do: _pow(x * x, div((n - 1), 2), x * y)

@doc """
Returns the non-negative square root of *x*.
Calculates the non-negative square root of *x*.
"""
@spec sqrt(x) :: float
defdelegate sqrt(x), to: :math

@doc """
Returns the non-negative nth-root of *x*.
Calculates the non-negative nth-root of *x*.
## Examples
Expand All @@ -139,7 +139,7 @@ defmodule Math do
def nth_root(x, n), do: pow(x, 1 / n)

@doc """
Returns the non-negative integer square root of *x* (rounded towards zero)
Calculates the non-negative integer square root of *x* (rounded towards zero)
Does not accept negative numbers as input.
Expand All @@ -154,6 +154,7 @@ defmodule Math do
iex> Math.isqrt(10)
3
"""
@spec isqrt(integer) :: integer
def isqrt(x)

def isqrt(x) when x < 0, do: raise ArithmeticError
Expand All @@ -174,6 +175,8 @@ defmodule Math do
This is the largest positive integer that divides both *a* and *b* without leaving a remainder.
Also see `Math.lcm/2`
## Examples
iex> Math.gcd(2, 4)
Expand All @@ -184,17 +187,23 @@ defmodule Math do
4
iex> Math.gcd(54, 24)
6
iex> Math.gcd(-54, 24)
6
"""
@spec gcd(integer, integer) :: non_neg_integer
def gcd(a, 0), do: abs(a)

def gcd(0, b), do: abs(b)
def gcd(a, b) when a < 0 or b < 0, do: gcd(abs(a), abs(b))
def gcd(a, b), do: gcd(b, rem(a,b))

@doc """
Calculates the Least Common Multiple of two numbers.
This is the smallest positive integer that can be divided by both *a* by *b* without leaving a remainder.
Also see `Math.gcd/2`
## Examples
iex> Math.lcm(4, 6)
Expand All @@ -204,11 +213,12 @@ defmodule Math do
iex> Math.lcm(21, 6)
42
"""
@spec lcm(integer, integer) :: non_neg_integer
def lcm(a, b)

def lcm(0, 0), do: 0
def lcm(a, b) do
Kernel.div(a * b, gcd(a, b))
abs(Kernel.div(a * b, gcd(a, b)))
end


Expand All @@ -227,11 +237,12 @@ defmodule Math do
iex> Math.factorial(20)
2432902008176640000
"""
@spec factorial(non_neg_integer) :: pos_integer
def factorial(n)

def factorial(0), do: 1

for {n, fact} <- (1..@precompute_factorials_up_to |> Enum.scan({0,1}, fn n, {_prev_n, prev_fact} -> {n, n * prev_fact} end)) do
for {n, fact} <- (1..@precompute_factorials_up_to |> Enum.scan( {0, 1}, fn n, {_prev_n, prev_fact} -> {n, n * prev_fact} end)) do
def factorial(unquote(n)), do: unquote(fact)
end

Expand All @@ -240,21 +251,71 @@ defmodule Math do
end

@doc """
Returns ℯ to the xth power.
Calculates the k-permutations of *n*.
This is the number of distinct ways to create groups of size *k* from *n* distinct elements.
Notice that *n* is the first parameter, for easier piping.
## Examples
iex> Math.k_permutations(10, 2)
90
iex> Math.k_permutations(5, 5)
120
iex> Math.k_permutations(3, 4)
0
"""
@spec k_permutations(non_neg_integer, non_neg_integer) :: non_neg_integer
def k_permutations(n, k)

def k_permutations(n, k) when k > n, do: 0

def k_permutations(n, k) do
div(factorial(n), factorial(n - k))
end


@doc """
Calculates the k-combinations of *n*.
## Examples
iex> Math.k_combinations(10, 2)
45
iex> Math.k_combinations(5, 5)
1
iex> Math.k_combinations(3, 4)
0
"""
@spec k_combinations(non_neg_integer, non_neg_integer) :: non_neg_integer
def k_combinations(n, k)

def k_combinations(n, k) when k > n, do: 0

def k_combinations(n, k) do
div(factorial(n), factorial(k) * factorial(n - k))
end


# Logarithms and exponentiation

@doc """
Calculates ℯ to the xth power.
"""
@spec exp(x) :: float
defdelegate exp(x), to: :math

@doc """
Returns the natural logarithm (base `ℯ`) of *x*.
Calculates the natural logarithm (base `ℯ`) of *x*.
See also `Math.e/0`.
"""
@spec log(x) :: float
defdelegate log(x), to: :math

@doc """
Returns the base-*b* logarithm of *x*
Calculates the base-*b* logarithm of *x*
Note that variants for the most common logarithms exist that are faster and more precise.
Expand All @@ -280,15 +341,15 @@ defmodule Math do
end

@doc """
Returns the binary logarithm (base `2`) of *x*.
Calculates the binary logarithm (base `2`) of *x*.
See also `Math.log/2`.
"""
@spec log2(x) :: float
defdelegate log2(x), to: :math

@doc """
Computes the common logarithm (base `10`) of *x*.
Calculates the common logarithm (base `10`) of *x*.
See also `Math.log/2`.
"""
Expand Down Expand Up @@ -366,6 +427,8 @@ defmodule Math do

@doc """
Computes the arc tangent given *y* and *x*. (expressed in radians)
This variant returns the inverse tangent in the correct quadrant, as the signs of both *x* and *y* are known.
"""
@spec atan2(y, x) :: float
defdelegate atan2(y, x), to: :math
Expand Down
85 changes: 81 additions & 4 deletions lib/math/enum.ex
@@ -1,19 +1,96 @@
defmodule Math.Enum do
@moduledoc """
Math functions that work on any Enumerable objects, such as lists, streams, maps, sets, etc.
Math.Enum defines Math-functions that work on any collection extending the Enumerable protocol.
This means Maps, Lists, Sets, etc., and any custom collection types as well.
"""

@doc """
Calculates the product, obtained by multiplying all elements in *collection* with eachother.
## Examples
iex> Math.Enum.product [1,2,3]
6
iex> Math.Enum.product 1..10
3628800
iex> Math.Enum.product [1,2,3,4,5, -100]
-12000
"""
def product(collection)

# General implementation for any enumerable.
def product(collection) do
Enum.reduce(collection, &(&1*&2))
Enum.reduce(collection, &(&1 * &2))
end

@doc """
Calculates the mean of a collection of numbers.
This is the sum, divided by the amount of elements in the collection.
If the collection is empty, returns `nil`
Also see `Math.Enum.median/1`
## Examples
iex> Math.Enum.mean [1,2,3]
2.0
iex> Math.Enum.mean 1..10
5.5
iex> Math.Enum.mean [1,2,3,4,5, -100]
-14.166666666666666
iex> Math.Enum.mean []
nil
"""
@spec mean(Enum.t) :: number
def mean(collection)

def mean(collection) do
count = Enum.count(collection)
case count do
0 -> nil
_ -> Enum.sum(collection) / count
end
end

@doc """
Calculates the median of a given collection of numbers.
- If the collection has an odd number of elements, this will be the middle-most element of the (sorted) collection.
- If the collection has an even number of elements, this will be mean of the middle-most two elements of the (sorted) collection.
If the collection is empty, returns `nil`
Also see `Math.Enum.mean/1`
## Examples
iex> Math.Enum.median [1,2,3]
2
iex> Math.Enum.median 1..10
6.5
iex> Math.Enum.median [1,2,3,4,5, -100]
3.5
iex> Math.Enum.median []
nil
"""
@spec median(Enum.t) :: number | nil
def median(collection)

def median(collection) do
count = Enum.count(collection)
cond do
count == 0 -> nil
rem(count, 2) == 1 -> # Middle element exists
Enum.sort(collection) |> Enum.at(div(count, 2))
true ->
# Take two middle-most elements.
sorted_collection = Enum.sort(collection)
[
Enum.at(sorted_collection, div(count, 2)),
Enum.at(sorted_collection, div(count, 2) + 1)
]
|> Math.Enum.mean
end
end
end

0 comments on commit b2745fe

Please sign in to comment.