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
fake_decimal README.md
Raw

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.