All notable changes to the NumberF library will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

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.

Mathpermutations/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).

Precisionclamp/3, safe_divide/3, sign/1, round_half_away_from_zero/2 and round_half_even/2.

Formattingaccounting_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.

Datesquarter/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/1factorial/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 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 alongside the HTML — an index whose every entry has a Markdown twin, so NumberF.md is the full API as plain text — and adds a "Copy Markdown" button to every page. A condensed llms.txt also ships at the repository root and in the Hex package.
  • Cheatsheet — every function on one page, with output verified against the library rather than transcribed.
  • FAQ guide.
  • 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 (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