Packages
A drop-in :decimal module reproducing the legacy erlang_decimal API on top of the modern Elixir Decimal library, so legacy code and the Elixir decimal app can coexist in one BEAM node.
Current section
Files
Jump to
Current section
Files
fake_decimal
README.md
README.md
# FakeDecimal
If you deal with legacy Erlang code, you may know the old `erlang_decimal`
library. On Hex it's published as `erlang_decimal`, but it registers its OTP
application (and its module) under the plain name `decimal` — the same name the
modern Elixir `decimal` library uses for its own OTP application. Since a BEAM
node can't run two applications with the same name, you can't depend on both at
once.
`fake_decimal` solves this by providing the exact same API as the old `decimal`
module, implemented on top of the modern Elixir `Decimal` library. Depend on
`fake_decimal` (which pulls in the real `decimal` app) instead of
`erlang_decimal`, and your legacy code can keep calling `:decimal.add/2`,
`:decimal.round/3`, and so on without ever knowing it's talking to the new
library underneath.
## Installation
Add `fake_decimal` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:fake_decimal, "~> 0.1.1"}
]
end
```
Documentation is available on [HexDocs](https://hexdocs.pm/fake_decimal).
## The `decimal.hrl` header
`erlang_decimal` ships an `include/decimal.hrl` with convenience macros
(`?add/2`, `?divide/2`, `?to_binary/1`, `?d_context`, …) that wrap the plain
`decimal` functions. `fake_decimal` ships the same header, unchanged — the
macros only expand to `decimal:...` calls, which is exactly what this library
provides.
The one thing you have to adjust in legacy modules is the include path, because
the OTP application is now called `fake_decimal`:
```erlang
%% -include_lib("decimal/include/decimal.hrl").
-include_lib("fake_decimal/include/decimal.hrl").
```
The knobs the header honours are unchanged too, so you can still override them
before including it:
```erlang
-define(d_precision, 20).
-define(d_rounding, round_down).
-define(d_pretty, false).
-define(d_no_autocast, true). %% skip the implicit ?to_decimal/1 on arguments
-include_lib("fake_decimal/include/decimal.hrl").
```
## Modules and types
Both modules of the original library are provided: `decimal` and
`decimal_conv`. The latter — parsing, formatting and float conversion — is a
direct port of the original Erlang source rather than a shim, because it is
what decides which strings are valid and which digits a float produces.
All four exported types are reproduced with identical definitions, so `-spec`s
in legacy modules keep resolving under Dialyzer:
```erlang
-spec total(decimal:decimal(), decimal:decimal()) -> decimal:decimal().
-spec context() -> decimal:opts().
-spec mode() -> decimal:rounding_algorithm().
-spec print_opts() -> decimal_conv:binary_opts().
```
## Known differences
Two remain, both of which only ever *accept more* or return an equal value, so
neither can break code that worked against the original:
* `add/2`, `sub/2` and `mult/2` return normalized tuples — `add({1,0}, {9,0})`
gives `{1,1}` where the original gives `{10,0}`. Numerically equal, and
`reduce/1` maps both to the same tuple, but exact pattern matches will see
the difference.
* Functions that take a `{coef, exp}` tuple in the original also accept the
old `{sign, coef, exp}` format, `Decimal` structs, integers, floats,
strings and charlists here.
Everything else — rounding, division, square root, comparison, formatting,
parsing, and the errors raised on invalid input — matches the original.
Property-based tests compare `fake_decimal` against a real instance of
`erlang_decimal`, loaded in a separate node, to make sure the two behave
identically.
See the @moduledoc in `lib/fake_decimal.ex` for the full API and the handful of
intentional deviations from the original.