Packages
Number formatting, currency and financial math for Elixir. Format currency and numbers for 22 locales, convert numbers to words and Roman numerals, calculate compound interest, EMI, VAT and income tax, run statistics, validate credit cards, and convert units. One zero-config dependency.
Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to the NumberF library will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.0] - 2026-08-22
Roughly 100 new functions, taking the public API from 103 arities to 261. No
breaking changes: everything in 0.2.0 keeps working.
### Added
**Statistics** (6 functions to 28) — `percentile/2`, `quartiles/1`, `iqr/1`,
`z_score/3`, `z_scores/1`, `correlation/2`, `covariance/2`, `linear_regression/2`,
`weighted_mean/2`, `geometric_mean/1`, `harmonic_mean/1`, `summary/1`,
`moving_average/2`, `cumulative_sum/1`, `normalize/1`, `outliers/2`,
`remove_outliers/2`, `frequency_distribution/1`, `trimmed_mean/2` and
`coefficient_of_variation/1`.
**Financial** (4 to 20) — `present_value/3`, `future_value/3`, `npv/2`, `irr/2`,
`amortization_schedule/3`, `roi/2`, `cagr/3`, `effective_annual_rate/2`,
`loan_payment/3`, the three annuity functions, straight-line and declining-balance
depreciation, `break_even_point/3` and `payback_period/2`.
`amortization_schedule/3` returns a full period-by-period breakdown and closes at
exactly zero — the final period absorbs the rounding, as a lender's schedule does.
`irr/2` solves by bisection and returns `{:error, :no_sign_change}` rather than a
meaningless number when the cash flows never cross zero.
**Validation** (3 to 14) — `valid_iban?/1` (mod-97), `valid_isbn?/1` (10 and 13),
`valid_ean?/1`, `valid_upc?/1`, `card_brand/1`, `luhn_check_digit/1`,
`valid_luhn?/1`, `valid_routing_number?/1`, `valid_imei?/1`,
`valid_currency_code?/1` and `valid_percentage?/1`.
**Math** — `permutations/2`, `fibonacci/1`, `fibonacci_sequence/1`,
`prime_factors/1`, `divisors/1`, `digits/1`, `digit_sum/1`, `digital_root/1`,
`reverse_number/1`, `palindrome?/1`, `perfect_square?/1`, `nth_root/2`,
`log_base/2`, and base conversion in any base from 2 to 36 (`to_base/2`,
`from_base/2` and hex/binary/octal shorthands).
**Precision** — `clamp/3`, `safe_divide/3`, `sign/1`,
`round_half_away_from_zero/2` and `round_half_even/2`.
**Formatting** — `accounting_format/2` (negatives in parentheses),
`format_bytes/2` (SI and binary, up to exabytes), `format_duration/2` (short, long
and clock forms), `significant_figures/2`, `format_significant/2`, `with_sign/2`,
`pad_number/3` and `pluralize/3`.
**Dates** — `quarter/1`, `quarter_start/1`, `quarter_end/1`, `fiscal_year/2`,
`days_in_month/2`, `leap_year?/1`, `week_number/1`, `month_start/1`, `month_end/1`
and `age_in_months/2`.
### Changed
- The generated documentation now groups functions into Formatting, Currency,
Text, Financial, Tax, Statistics, Precision, Validation, Math, Units, Dates,
Humanize, Internationalization and Introspection. The grouping is generated from
`@doc group:` metadata on each function, so the sidebar cannot drift from the code.
- Still exactly one runtime dependency. Every function above is implemented with
`:math` and the standard library.
### Fixed
- `I18n.spell_number/3` raised `FunctionClauseError` for any value of 1,000,000 or
more in French, Spanish and German — only the English speller had a millions
clause. Found by the first tests ever written against those three languages.
## [0.2.0] - 2026-08-22
Documentation, discoverability, engineering hygiene — and 16 correctness fixes.
The public API is source-compatible with 0.1.8 except where noted under
**Behaviour changes**; those are all cases where the previous result was wrong.
### Fixed
Every item below was reproduced against a compiled build before being fixed, and
each has a named regression test in `test/number_f/regressions_test.exs`.
**Silently wrong results**
- `I18n.spell_number/3` destroyed leading zeros in a fraction. `spell_number(1.05, "en")`
and `spell_number(1.5, "en")` both returned `"One point five"` — `split_number/1`
ran `String.to_integer("05")`. Fractions are now read digit by digit, so 1.05 is
`"One point zero five"`. Currency minor units are read positionally, so 42.5 is
`"forty-two dollars and fifty cents"`, not five.
- `I18n.spell_number/3` silently fell back to English for any unrecognised language,
so 18 of the 22 advertised locales produced confidently wrong output. It now raises
`ArgumentError` naming the four languages that work. Added
`I18n.supported_languages/0` and `NumberF.spelling_languages/0`.
- `I18n.spell_number/3` emitted a double space wherever the conjunction was empty
(`"One thousand two hundred"`).
- `abbreviate_number/2` had no tier above billions, so a trillion rendered as
`"1.5e3B"`. It also never matched a negative number, so `-5000` came back as
`"-5000"` rather than `"-5.0K"`.
- `memory_size_cal/1` capped at GB (5 TB showed as `"4656.61 GB"`) and routed
negatives through the bytes branch. Now handles up to PB.
- `Formatter.scientific_notation/2` and `Formatter.engineering_notation/2`
hardcoded their own doctest input — `if abs(number - 0.000123) < 1.0e-10` returned
a canned string. The doctests passed while testing nothing. The underlying defect
was `trunc/1`, which rounds toward zero and so gives the wrong exponent for any
magnitude below 1; both now use a floored base-10 exponent.
- `Calculations.is_prime?/1`, `factorial/1` — `factorial/1` is now tail-recursive
and rejects negative input with `ArgumentError` instead of recursing forever.
- `Randomizer.randomizer/2` with `:alpha` returned letters *and* digits, making it
identical to the catch-all. The one option documented as letters-only never worked.
- `DateCalculations.business_days_between/2` built a descending range for a reversed
date pair, which emitted a `Range` deprecation warning at runtime and returned a
positive count. It now returns a signed result.
- `calculate_age/1` raised `KeyError` on Elixir 1.18 and `BadMapError` on 1.20 for
non-`Date` input; it now raises `ArgumentError` on every version. Caught by the
new CI version matrix on its first run.
**Crashes on valid input**
- `calculate_emi/3` raised `ArithmeticError` for a 0% interest rate — both the
numerator and `power_term - 1` are zero. Interest-free loans now spread the
principal evenly across the term.
- `to_int/1` raised `FunctionClauseError` on an integer, rejecting the very type it
returns. It now accepts integers, floats and strings.
- `round_with_precision/2` was guarded on `is_number/1` but called `Float.round/2`,
which requires a float, so any integer raised `FunctionClauseError`.
- `percentage/3` had no zero-divisor guard and leaked `ArithmeticError`.
- `DateCalculations.add_business_days/2` was guarded on `num_days >= 0`, so
subtracting business days raised `FunctionClauseError`.
**Shipped artefacts**
- Removed `NumbersToWords.try/0` — a public, zero-arity debug leftover hardcoded to
`Decimal.new("2.5")` that was shipped in every release since 0.1.0.
- Removed `NumberF.Application`. It was an empty supervisor whose only observable
effect was making `Supervisor.start_link/2` return `{:error, {:already_started, _}}`.
The `mod:` key is gone from `mix.exs`; a pure computation library needs no
supervision tree.
- The dev-only `test.numberf_fast` and `test.numberf_all` mix tasks are excluded from
the Hex package. They were previously installed into every consuming project.
**Repository**
- **CI had never run.** `ci.yml` and `elixir.yml` were both triggered on
`push: [main]`; the default branch is `master`. Workflows are rebuilt as `ci.yml`
(lint / matrix test / package) and `publish.yml`, on `master`.
- The old CI `test` job never invoked `mix test`, and its Credo and Dialyzer steps
were `continue-on-error` guards around tools that were not dependencies.
- Removed the orphaned `:excoveralls` entry from `mix.lock`, which had been failing
the `mix deps.unlock --check-unused` gate unnoticed.
- Moved `.github/README.md` to `.github/WORKFLOWS.md`: GitHub renders a README in
`.github/` in preference to the repository root, so the project's landing page was
showing CI documentation instead of the library.
- Corrected the `LICENSE` file so GitHub detects it as MIT.
- Repository links pointed at a non-existent `main` branch; they now use `master`.
This fixes the broken Changelog link on hex.pm.
- Corrected the documented locale count from "25+" to the actual 22, and five
documented examples whose stated output did not match the library.
### Changed — the facade
`lib/number_f.ex` was 1,879 lines, 72% of it documentation duplicated from the
submodules, with 88 `def` and zero `defdelegate`. Thirty-nine of those functions
reimplemented logic the submodules already contained, which is why
`NumberF.Validation`, `NumberF.Financial` and `NumberF.Formatter` had no callers at
all and had quietly drifted from their facade twins.
- Every public function is now a `defdelegate`; the facade holds the canonical
documentation and the submodules hold the single implementation. The two can no
longer disagree.
- Reconciled the drift, with the facade's behaviour taking precedence because it is
what the documentation promised: `Calculations.round_to_nearest/2` now returns a
float (was an integer), and `Formatter.to_roman/1` raises `ArgumentError` outside
1..3999 (was `FunctionClauseError`).
- **All 36 previously unreachable submodule functions are now callable from
`NumberF`** — every `NumberF.Tax` calculation, `Metrics.convert_units/4` and the
four unit tables, `Precision.truncate/2`, `precise_format/2`, `custom_round/3` and
`sanitize_float/2`, `I18n.parse_number/2` and `get_locale_settings/1`,
`Currencies.convert/5` and `parse/2`, `DateCalculations.add_business_days/2` and
`next_business_day/1`, and the `Registry` detail lookups. A test now derives the
expected surface from the submodules, so a new one that is not delegated fails CI.
- `to_int/1`, `to_boolean/1` and `sum_decimal/1` moved to `NumberF.CustomFormatter`,
alongside the conversions that were already there.
- `NumberF.Statistics` gained the input validation that only the facade copy had.
- The library version is no longer hardcoded in `reference/0`; it is read from
`mix.exs`.
### Behaviour changes
These change results that were previously wrong. No source change is required.
- `combinations/2` returns an `t:integer/0` rather than a float, computed with the
multiplicative formula instead of three factorials. `combinations(30, 15)` was
`155117520.0` and is now `155_117_520`; `combinations(100, 50)` is now exact
rather than a float approximation.
- Invalid input to `simple_interest/3`, `compound_interest/4`, `calculate_emi/3`,
`convert_currency/3`, `percentage/3` and `to_int/1` raises `ArgumentError` naming
the offending argument, instead of leaking `FunctionClauseError` or
`ArithmeticError`.
- `I18n.spell_number/3` raises for an unsupported language instead of returning
English.
### Added
- **LLM-readable documentation.** ExDoc 0.40 publishes
[`llms.txt`](https://hexdocs.pm/number_f/llms.txt) and
[`llms-full.txt`](https://hexdocs.pm/number_f/llms-full.txt) alongside the HTML,
and adds a "Copy Markdown" button to every page. A condensed `llms.txt` ships at
the repository root and in the Hex package.
- [Cheatsheet](https://hexdocs.pm/number_f/cheatsheet.html) — every function on one
page, with output verified against the library rather than transcribed.
- [FAQ guide](https://hexdocs.pm/number_f/faq.html).
- `Statistics.sample_variance/1` and `Statistics.sample_standard_deviation/1`, with
facade delegates. `variance/1` and `standard_deviation/1` remain the population
forms; the docs now say which is which.
- `I18n.supported_languages/0` and `NumberF.spelling_languages/0`.
- Credo (strict, with an explicit check allowlist), Dialyzer, and ExCoveralls with a
ratcheted minimum. `mix lint` runs the same gates CI does.
- `AGENTS.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, GitHub issue forms and a pull
request template.
- Static SEO and schema.org metadata in the generated docs, plus a canonical URL and
a favicon.
### Changed
- `decimal` requirement widened to `~> 2.0 or ~> 3.0` so consumers can take the fix
for [GHSA-rhv4-8758-jx7v](https://github.com/advisories/GHSA-rhv4-8758-jx7v)
(unbounded exponent DoS, `decimal < 3.0.0`). This library uses only
`new/to_float/from_float/to_string/round/add`, stable across both majors; the
suite is verified green against both 2.3.0 and 3.1.1. Resolving 3.x also requires
`jason >= 1.4.5` in the dev/test closure — 1.4.4 declared
`decimal ~> 1.0 or ~> 2.0` and held the entire resolution below 3.
- Rewrote `README.md`, which previously duplicated the Getting Started guide.
- Guides ship inside the package tarball.
- `NumberF.NumbersToWords` and `NumberF.NumberToWord` are marked `@moduledoc false`.
They were never documented and are internal to `to_words/3`; they previously
appeared in the docs as empty pages. Their functions are unchanged.
- Applied `mix format` across the project; `mix format --check-formatted` had been
failing.
## [0.1.8] - 2025-05-23
### Changed
- Refactored number formatting and utility functions.
## [0.1.7] - 2025-05-23
### Added
- Comprehensive test suite covering unit, property, integration, concurrency, and
performance scenarios.
## [0.1.6] - 2025-05-19
### Added
- Custom number formatting with `Decimal` support, removing the `number` dependency.
- Custom delimiter handling for integer inputs.
## [0.1.5] - 2025-05-17
### Added
- **Internationalization Module** (`NumberF.I18n`)
- Added locale-specific number formatting for 22 locales
- Added multi-language number spelling (English, French, Spanish, German)
- Added currency-specific formatting rules
- **Metrics Module** (`NumberF.Metrics`)
- Added conversion between metric and imperial units
- Added temperature conversion (Celsius/Fahrenheit)
- Added customizable unit conversion framework
- **Tax Module** (`NumberF.Tax`)
- Added VAT calculation with inclusive/exclusive options
- Added sales tax calculation with configurable rounding
- Added income tax calculation with progressive brackets
- Added capital gains tax calculation
- Added withholding tax calculation
- Added corporate tax calculation
- Added payroll tax calculation
- **Precision Module** (`NumberF.Precision`)
- Added bankers rounding (round to even)
- Added custom rounding for different thresholds
- Added approximate equality testing for floating point
- Added sanitization for special values (NaN, Infinity)
- **Currencies Module** (`NumberF.Currencies`)
- Added comprehensive currency information database
- Added currency-specific formatting rules
- Added multi-currency conversion framework
- **Registry Module** (`NumberF.Registry`)
- Added module and function discovery utilities
- Added documentation generator
### Improved
- **Core Module** (`NumberF`)
- Enhanced organization for better discoverability
- Added direct access to functionality from submodules
- Improved documentation with detailed examples
- Fixed naming conflicts with Kernel functions
- **Documentation**
- Added comprehensive examples
- Categorized functions for easier navigation
- Added cross-references between related functions
- More detailed parameter descriptions
### Fixed
- Resolved precision issues in floating-point calculations
- Fixed currency symbol placement for different locales
- Addressed multiple default parameter declaration issues
## [0.1.4] - 2025-01-20
### Added
- Initial release with basic functionality
- Currency formatting
- Number to words conversion
- Financial calculations
- Statistical functions
- Memory size formatting
- Random string generation
- Basic type conversion utilities
[0.3.0]: https://github.com/jamesnjovu/elixir_number_functions/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/jamesnjovu/elixir_number_functions/compare/v0.1.8...v0.2.0
[0.1.8]: https://github.com/jamesnjovu/elixir_number_functions/compare/v0.1.7...v0.1.8
[0.1.7]: https://github.com/jamesnjovu/elixir_number_functions/compare/v0.1.6...v0.1.7
[0.1.6]: https://github.com/jamesnjovu/elixir_number_functions/compare/v0.1.5...v0.1.6
[0.1.5]: https://github.com/jamesnjovu/elixir_number_functions/releases/tag/v0.1.5
[0.1.4]: https://github.com/jamesnjovu/elixir_number_functions/releases/tag/v0.1.4