README.md

# Apa

![Elixir CI](https://github.com/razuf/apa/workflows/Elixir%20CI/badge.svg)

APA : Arbitrary Precision Arithmetic - pure Elixir implementation.

For arbitrary precision mathematics - which supports numbers of any size and precision up to nearly unlimited of decimals (internal Elixir integer math), represented as strings. Inspired by BCMath/PHP.
This is especially useful when working with floating-point numbers, as these introduce small but in some case significant rounding errors.

## Intention, Pro & Cons

I started this project to learn for myself - so the focus was on learning and have fun!
You could use it if you like - there are some test coverage - but for production I would recomend the [Decimal](https://github.com/ericmj/decimal) package!


Some limits and 'bugs' in standard Erlang/Elixir:

```elixir
iex> 0.30000000000000004 - 0.30000000000000003
0.0
```

with Apa:
```elixir
"0.30000000000000004" - "0.30000000000000003"
"0.00000000000000001"
```

Elixir:
```elixir
iex> 0.1 + 0.2
0.30000000000000004
```

with Apa:
```elixir
"0.1" + "0.2"
"0.3"
```

Elixir:
```elixir
iex> 9007199254740992.0 - 9007199254740991.0
1.0
iex> 9007199254740993.0 - 9007199254740992.0
0.0
iex> 9007199254740994.0 - 9007199254740993.0
2.0

iex> 87654321098765432.0 - 87654321098765431.0
16.0

iex> 0.123456789e-100 * 0.123456789e-100
1.524157875019052e-202
iex> 0.123456789e-200 * 0.123456789e-200
0.0

iex> :math.pow(2, 1500)
** (ArithmeticError) bad argument in arithmetic expression
```

On a short research I found the existing lib EAPA have some limits and disadvantages:

[EAPA](https://github.com/Vonmo/eapa) (Erlang/Elixir Arbitrary-Precision Arithmetic)
a) Customized precision up to 126 decimal places (current realization)
Why only 126 decimal places? Apa should not have that limit!

b) EAPA is a NIF extension written on Rust -> performance fine, but bad in case of dependencies f.e. for [Nerves](https://www.nerves-project.org/).
Apa is in pure Elixir with no dependency - running on any [Nerves device](https://hexdocs.pm/nerves/targets.html/).

Later I found [Decimal](https://github.com/ericmj/decimal) which looks very nice and useful (written by Eric Meadows-Jönsson!) - so there is already a solution - nice, stable and full featured!
I used it in Phoenix with Ecto without thinking about it ... but that's life.

Anyway I had fun with Apa on Eastern 2020. ;-)

A little feature I could offer compared to [Decimal](https://github.com/ericmj/decimal) (but of course could be easily expanded there too)

```elixir
"0.30000000000000004" - "0.30000000000000003"
"0.00000000000000001"
```

Or calc and compare directly with strings in case of ecto/database

with Decimal:

```elixir
schema "products" do
  field :name, :string
  field :price, :decimal
  timestamps()
end

%Product{
  name: "Apple",
  price: 3,
}
cart_total = Decimal.to_string(Decimal.mult(Decimal.new(product.price), Decimal.new(cart_quantity)))
```

with Apa:

```elixir
schema "product" do
  field :name, :string
  field :price, :string
  timestamps()
end

%Product{
  name: "Apple",
  price: "3",
}
cart_total = product.price * cart_quantity
```

Could be useful with [CubDB](https://github.com/lucaong/cubdb) (pure Elixir key/value database).

## Features

  A list of supported and planned features (maybe incomplete)

  - [x] basic operations (`add`)
  - [x] basic operations (`sub`)
  - [x] basic operations (`mul`)
  - [x] basic operations (`div`)
  - [x] comparison (`comp`)
  - [ ] scale (number of digits after the decimal place in the result)
  - [ ] rounding
  - [ ] Infinity and NaN
  - [ ] string format for result
  - [ ] performance - f.e. benchee check - this pure Elixir implementation looks like fast enough for normal applications (normal means not for number crunching)

## Installation

  1. Add `apa` to your list of dependencies in `mix.exs`:

  ```elixir
  def deps do
    [
      {:apa, "~> 0.3.0"}
    ]
  end
  ```

## Usage

  ```elixir
  defmodule ApaExample do
    import Apa
    import Kernel, except: [+: 2, -: 2, *: 2, /: 2, to_string: 1]

    def the_answer() do
      apa1 = Apa.add("1", "2")
      apa2 = Apa.sub("3", "2")

      price = "3.50 Euro"
      quantity = "12"
      total_string = price * quantity

      IO.puts("The Answer to the Ultimate Question of Life, the Universe, and Everything is: ")

      "1"
      |> Apa.add("2")
      |> Apa.add("3")
      |> Apa.sub("4")
      |> Apa.add("5")
      |> Apa.mul("6")
    end
  end
  ```


## Examples

```elixir
iex> Apa.add("0.1", "0.2")
"0.3"
iex> Apa.sub("3.0", "0.000000000000000000000000000000000000000000000001")
"2.999999999999999999999999999999999999999999999999"
iex> "333.33" |> Apa.add("666.66") |> Apa.sub("111.11")
"888.88"

iex> "1" |> Apa.add("2") |> Apa.add("3") |> Apa.sub("4") |> Apa.add("5") |> Apa.mul("6")
"42"
```
:laughing: