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.
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
:mathand the standard library.
Fixed
I18n.spell_number/3raisedFunctionClauseErrorfor 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/3destroyed leading zeros in a fraction.spell_number(1.05, "en")andspell_number(1.5, "en")both returned"One point five"—split_number/1ranString.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/3silently fell back to English for any unrecognised language, so 18 of the 22 advertised locales produced confidently wrong output. It now raisesArgumentErrornaming the four languages that work. AddedI18n.supported_languages/0andNumberF.spelling_languages/0.I18n.spell_number/3emitted a double space wherever the conjunction was empty ("One thousand two hundred").abbreviate_number/2had no tier above billions, so a trillion rendered as"1.5e3B". It also never matched a negative number, so-5000came back as"-5000"rather than"-5.0K".memory_size_cal/1capped at GB (5 TB showed as"4656.61 GB") and routed negatives through the bytes branch. Now handles up to PB.Formatter.scientific_notation/2andFormatter.engineering_notation/2hardcoded their own doctest input —if abs(number - 0.000123) < 1.0e-10returned a canned string. The doctests passed while testing nothing. The underlying defect wastrunc/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/1is now tail-recursive and rejects negative input withArgumentErrorinstead of recursing forever.Randomizer.randomizer/2with:alphareturned letters and digits, making it identical to the catch-all. The one option documented as letters-only never worked.DateCalculations.business_days_between/2built a descending range for a reversed date pair, which emitted aRangedeprecation warning at runtime and returned a positive count. It now returns a signed result.calculate_age/1raisedKeyErroron Elixir 1.18 andBadMapErroron 1.20 for non-Dateinput; it now raisesArgumentErroron every version. Caught by the new CI version matrix on its first run.
Crashes on valid input
calculate_emi/3raisedArithmeticErrorfor a 0% interest rate — both the numerator andpower_term - 1are zero. Interest-free loans now spread the principal evenly across the term.to_int/1raisedFunctionClauseErroron an integer, rejecting the very type it returns. It now accepts integers, floats and strings.round_with_precision/2was guarded onis_number/1but calledFloat.round/2, which requires a float, so any integer raisedFunctionClauseError.percentage/3had no zero-divisor guard and leakedArithmeticError.DateCalculations.add_business_days/2was guarded onnum_days >= 0, so subtracting business days raisedFunctionClauseError.
Shipped artefacts
- Removed
NumbersToWords.try/0— a public, zero-arity debug leftover hardcoded toDecimal.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 makingSupervisor.start_link/2return{:error, {:already_started, _}}. Themod:key is gone frommix.exs; a pure computation library needs no supervision tree. - The dev-only
test.numberf_fastandtest.numberf_allmix tasks are excluded from the Hex package. They were previously installed into every consuming project.
Repository
- CI had never run.
ci.ymlandelixir.ymlwere both triggered onpush: [main]; the default branch ismaster. Workflows are rebuilt asci.yml(lint / matrix test / package) andpublish.yml, onmaster. - The old CI
testjob never invokedmix test, and its Credo and Dialyzer steps werecontinue-on-errorguards around tools that were not dependencies. - Removed the orphaned
:excoverallsentry frommix.lock, which had been failing themix deps.unlock --check-unusedgate unnoticed. - Moved
.github/README.mdto.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
LICENSEfile so GitHub detects it as MIT. - Repository links pointed at a non-existent
mainbranch; they now usemaster. 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/2now returns a float (was an integer), andFormatter.to_roman/1raisesArgumentErroroutside 1..3999 (wasFunctionClauseError). - All 36 previously unreachable submodule functions are now callable from
NumberF— everyNumberF.Taxcalculation,Metrics.convert_units/4and the four unit tables,Precision.truncate/2,precise_format/2,custom_round/3andsanitize_float/2,I18n.parse_number/2andget_locale_settings/1,Currencies.convert/5andparse/2,DateCalculations.add_business_days/2andnext_business_day/1, and theRegistrydetail 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/1andsum_decimal/1moved toNumberF.CustomFormatter, alongside the conversions that were already there.NumberF.Statisticsgained the input validation that only the facade copy had.- The library version is no longer hardcoded in
reference/0; it is read frommix.exs.
Behaviour changes
These change results that were previously wrong. No source change is required.
combinations/2returns aninteger/0rather than a float, computed with the multiplicative formula instead of three factorials.combinations(30, 15)was155117520.0and is now155_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/3andto_int/1raisesArgumentErrornaming the offending argument, instead of leakingFunctionClauseErrororArithmeticError. I18n.spell_number/3raises for an unsupported language instead of returning English.
Added
- LLM-readable documentation. ExDoc 0.40 publishes
llms.txtalongside the HTML — an index whose every entry has a Markdown twin, soNumberF.mdis the full API as plain text — and adds a "Copy Markdown" button to every page. A condensedllms.txtalso 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/1andStatistics.sample_standard_deviation/1, with facade delegates.variance/1andstandard_deviation/1remain the population forms; the docs now say which is which.I18n.supported_languages/0andNumberF.spelling_languages/0.- Credo (strict, with an explicit check allowlist), Dialyzer, and ExCoveralls with a
ratcheted minimum.
mix lintruns 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
decimalrequirement widened to~> 2.0 or ~> 3.0so consumers can take the fix for GHSA-rhv4-8758-jx7v (unbounded exponent DoS,decimal < 3.0.0). This library uses onlynew/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 requiresjason >= 1.4.5in the dev/test closure — 1.4.4 declareddecimal ~> 1.0 or ~> 2.0and held the entire resolution below 3.- Rewrote
README.md, which previously duplicated the Getting Started guide. - Guides ship inside the package tarball.
NumberF.NumbersToWordsandNumberF.NumberToWordare marked@moduledoc false. They were never documented and are internal toto_words/3; they previously appeared in the docs as empty pages. Their functions are unchanged.- Applied
mix formatacross the project;mix format --check-formattedhad 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
Decimalsupport, removing thenumberdependency. - 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