Packages

A comprehensive Elixir library for number formatting, calculation, and internationalization. Features include currency formatting, number-to-word conversion, financial calculations, statistics, unit conversion, and tax calculations.

Current section

Files

Jump to
number_f lib number_f.ex
Raw

lib/number_f.ex

defmodule NumberF do
@moduledoc """
# NumberF
A comprehensive utility library for number formatting, calculation, and manipulation in Elixir.
## Features
- **Number Formatting**: Format numbers as currency, with commas, custom delimiters, abbreviated forms (K, M, B)
- **Text Representation**: Convert numbers to words, ordinals, Roman numerals
- **Financial Calculations**: Simple/compound interest, EMI calculations, currency conversions, tax calculations
- **Statistical Functions**: Mean, median, mode, standard deviation, variance
- **Date Calculations**: Age calculation, business days, payment terms
- **Validation**: Credit card validation (Luhn algorithm), number format validation
- **String Generation**: Random string generation, password creation
- **Type Conversions**: Convert between numeric types and formats
- **Memory Size**: Convert byte counts to human-readable formats
- **Phone Formatting**: Format phone numbers based on country codes
- **Unit Conversion**: Convert between metric and imperial units
- **Tax Calculations**: VAT, sales tax, income tax calculations
- **Precision Handling**: Different rounding strategies and precision handling
- **Internationalization**: Format numbers according to locale conventions
## Installation
Add `number_f` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:number_f, "~> 0.2.0"}
]
end
```
## Key Functionality
### Currency & Number Formatting
NumberF provides comprehensive formatting capabilities for currency and numbers:
```elixir
# Format as currency with default (ZMW)
NumberF.currency(1234.567) # => "ZMW 1,234.57"
# Format as US Dollars with 2 decimal places
NumberF.currency(1234.567, "USD", 2) # => "USD 1,234.57"
# Format with French locale (space as thousands separator, comma as decimal)
NumberF.format_number(1234567.89, "fr-FR") # => "1 234 567,89"
```
### Number to Words Conversion
Convert numeric values to their word representations:
```elixir
# Convert whole number with default currency (Kwacha)
NumberF.to_words(42) # => "Forty Two Kwacha"
# Convert decimal with custom currency
NumberF.to_words(42.75, "Dollars", "Cents") # => "Forty Two Dollars And Seventy Five Cents"
# Advanced internationalization
NumberF.spell_number(42, "fr") # => "Quarante-deux"
```
### Financial Calculations
Perform complex financial operations with ease:
```elixir
# Calculate simple interest (principal, rate, time)
NumberF.simple_interest(1000, 0.05, 2) # => 100.0
# Calculate compound interest with monthly compounding
NumberF.compound_interest(1000, 0.05, 2, 12) # => 104.94
# Calculate loan EMI (principal, rate, term in months)
NumberF.calculate_emi(100000, 0.10, 12) # => 8791.59
# Calculate VAT
NumberF.calculate_vat(100, 0.2) # => %{net: 100.0, vat: 20.0, gross: 120.0}
```
### Statistical Functions
Analyze numerical data with statistical functions:
```elixir
NumberF.mean([1, 2, 3, 4, 5]) # => 3.0
NumberF.median([1, 3, 5, 7, 9]) # => 5
NumberF.mode([1, 2, 2, 3, 3, 3, 4]) # => [3]
NumberF.standard_deviation([2, 4, 4, 4, 5, 5, 7, 9]) # => 2.0
```
### Internationalization & Localization
Format numbers according to locale-specific conventions:
```elixir
# Format with US locale
NumberF.format_currency(1234.56, "en-US") # => "$1,234.56"
# Format with French locale
NumberF.format_currency(1234.56, "fr-FR") # => "1 234,56 €"
# Format with German locale but using USD
NumberF.format_currency(1234.56, "de-DE", currency_code: "USD") # => "1.234,56 $"
```
## Module Organization
NumberF is organized into specialized modules for different functionality areas:
- **Core**: `NumberF`, `NumberF.Registry`
- **Calculation**: `NumberF.Calculations`, `NumberF.Financial`, `NumberF.Statistics`, `NumberF.Precision`, `NumberF.Tax`
- **Formatting**: `NumberF.Currency`, `NumberF.Formatter`, `NumberF.CustomFormatter`, `NumberF.Currencies`
- **Conversion**: `NumberF.Metrics`, `NumberF.NumbersToWords`, `NumberF.NumberToWord`
- **Validation**: `NumberF.Validation`
- **Utilities**: `NumberF.Memory`, `NumberF.Randomizer`, `NumberF.Helper`
- **Date**: `NumberF.DateCalculations`
- **I18n**: `NumberF.I18n`
While specific functions can be accessed through their respective modules,
most common functionality is available directly from the main `NumberF` module
for convenience.
For more information, see the [full documentation on HexDocs](https://hexdocs.pm/number_f).
"""
@doc """
Formats a number into currency with the specified unit and precision.
## Features
- Automatic digit grouping with thousands separators
- Configurable decimal precision
- Support for custom currency symbols/codes
- Consistent formatting for financial applications
## Parameters
- `number`: The number to format
- `unit`: The currency unit (default: "ZMW")
- `precision`: Decimal places (default: 2)
## Examples
iex> NumberF.currency(1234.567)
"ZMW 1,234.57"
iex> NumberF.currency(1234.567, "USD", 2)
"USD 1,234.57"
iex> NumberF.currency(1234567.89, "€", 0)
"€ 1,234,568"
iex> NumberF.currency(nil, "USD", 2)
nil
## Common Use Cases
- Financial reporting and analysis
- E-commerce price displays
- Invoice and receipt generation
- Banking and financial applications
## Related Functions
- `NumberF.comma_separated/2`: Format numbers with commas but no currency
- `NumberF.format_currency/3`: Format with locale-specific currency settings
- `NumberF.number_to_delimited/2`: Format with custom delimiters
See also: `NumberF.Currencies` for currency-specific information.
"""
def currency(number, unit \\ "ZMW", precision \\ 2),
do: NumberF.Currency.currency(number, unit, precision)
@doc """
Formats a number into comma-separated format with the specified precision.
## Parameters
- `number`: The number to format
- `precision`: Decimal places (default: 2)
## Examples
iex> NumberF.comma_separated(1234567.89)
"1,234,567.89"
iex> NumberF.comma_separated(1234567.89, 0)
"1,234,568"
iex> NumberF.comma_separated(nil, 2)
nil
"""
def comma_separated(number, precision \\ 2),
do: NumberF.Currency.comma_separated(number, precision)
@doc """
Generates a random string of the specified length.
## Parameters
- `length`: The length of the string
- `type`: Type of string, with options:
- `:all` (alphanumeric) - default
- `:alpha` (alphabetical)
- `:numeric` (numbers)
- `:upcase` (uppercase)
- `:downcase` (lowercase)
## Examples
iex> NumberF.randomizer(10)
"a1B2c3D4e5" # Example output, actual value will vary
iex> NumberF.randomizer(5, :numeric)
"23579" # Example output, actual value will vary
iex> NumberF.randomizer(6, :upcase)
"ABCDEF" # Example output, actual value will vary
"""
def randomizer(length, type \\ :all),
do: NumberF.Randomizer.randomizer(length, type)
@doc """
Generates a default password with pre-defined complexity.
The pattern is: capitalized 3-letter string + @ + 4 random digits.
## Examples
iex> NumberF.default_password()
"Dev@2308" # Example output, actual value will vary
"""
def default_password(), do: NumberF.Randomizer.gen_password()
@doc """
Converts a number into words with customizable currency terms.
## Features
- Convert integers and decimals to their word representations
- Support for custom currency terms for main and fractional units
- Handles numbers from zero to trillions
- Properly handles decimal values and zero cases
## Parameters
- `amount`: The number to convert
- `main_currency`: The main currency name (default: "Kwacha")
- `sec_currency`: The secondary currency name (default: "Ngwee")
## Examples
iex> NumberF.to_words(20.0)
"Twenty Kwacha and zero Ngwee"
iex> NumberF.to_words(42.75, "Dollars", "Cents")
"Forty Two Dollars And Seventy Five Cents"
iex> NumberF.to_words(1234567.89, "Euros", "Cents")
"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven Euros And Eighty Nine Cents"
iex> NumberF.to_words(0, "Euros", "Cents")
"zero Euros"
## Common Use Cases
- Check writing and financial documents
- Legal documents requiring numeric values in words
- Invoices and receipts
- Educational applications
## Related Functions
- `NumberF.spell_number/3`: Spell numbers in different languages
- `NumberF.to_roman/1`: Convert to Roman numerals
- `NumberF.ordinal/1`: Convert to ordinal forms (1st, 2nd, 3rd)
See also: `NumberF.NumbersToWords` for more advanced word conversion options.
"""
def to_words(amount, main_currence \\ "Kwacha", sec_current \\ "Ngwee"),
do: NumberF.NumbersToWords.parse(amount, main_currence, sec_current)
@doc """
Converts a memory size in bytes to a human-readable format.
Automatically selects the appropriate unit (B, KB, MB, GB) based on size.
## Parameters
- `size`: The size in bytes
## Examples
iex> NumberF.memory_size_cal(500)
"500 B"
iex> NumberF.memory_size_cal(1024)
"1.0 KB"
iex> NumberF.memory_size_cal(1048576)
"1.0 MB"
iex> NumberF.memory_size_cal(1073741824)
"1.0 GB"
"""
def memory_size_cal(size),
do: NumberF.Memory.humanize(size)
@doc """
Converts a string to an integer.
## Parameters
- `value`: The string value to convert
## Examples
iex> NumberF.to_int("123")
123
iex> NumberF.to_int("123.45")
123
"""
def to_int(value) do
{nun, _} = Integer.parse(value)
nun
end
@doc """
Converts a value to a float.
## Parameters
- `value`: The value to convert (string or number)
## Examples
iex> NumberF.to_float("123.45")
123.45
iex> NumberF.to_float(123)
123.0
"""
def to_float(value), do: NumberF.CustomFormatter.to_float(value)
@doc """
Converts a value to a decimal.
## Parameters
- `value`: The value to convert (string or number)
## Examples
iex> NumberF.to_decimal("123.45")
#Decimal<123.45>
iex> NumberF.to_decimal(123)
#Decimal<123>
"""
def to_decimal(value), do: NumberF.CustomFormatter.to_decimal(value)
@doc """
Converts a string to a boolean value.
## Parameters
- `value`: The string value to convert. Accepts the following:
- `true`, `yes`, `on` convert to `true`
- `false`, `no`, `off` convert to `false`
- Any other value raises an ArgumentError
## Examples
iex> NumberF.to_boolean("true")
true
iex> NumberF.to_boolean("yes")
true
iex> NumberF.to_boolean("false")
false
iex> NumberF.to_boolean("no")
false
"""
def to_boolean(value) do
target = String.downcase(value)
cond do
target == "true" || target == "yes" || target == "on" -> true
target == "false" || target == "no" || target == "off" -> false
true -> raise ArgumentError, "could not convert #{inspect(value)} to boolean"
end
end
@doc """
Formats a number into a delimited format with options for customization.
## Parameters
- `number`: The number to format
- `options`: Keyword options for formatting:
- `delimiter`: Character used as thousand delimiter (default: ",")
- `separator`: Character used as decimal separator (default: ".")
- `precision`: Number of decimal places (default: 2)
## Examples
iex> NumberF.number_to_delimited(1234567.89)
"1,234,567.89"
iex> NumberF.number_to_delimited(1234567.89, delimiter: ".", separator: ",")
"1.234.567,89"
iex> NumberF.number_to_delimited(1234567.89, precision: 0)
"1,234,568"
"""
def number_to_delimited(number, options \\ []),
do: NumberF.CustomFormatter.number_to_delimited(number, options)
@doc """
Sums a list of decimal numbers.
## Parameters
- `list`: A list of decimal values, potentially nested
## Examples
iex> sum_decimal([Decimal.new("1.2"), Decimal.new("3.4"), [Decimal.new("5.6")]])
#Decimal<10.2>
iex> sum_decimal([])
#Decimal<0>
This function flattens any nested lists, then uses `Enum.reduce/3` to sum all the decimal values.
"""
def sum_decimal(list) do
List.flatten(list)
|> Enum.reduce(Decimal.new(0), &Decimal.add(&1, &2))
end
### NUMERIC CALCULATIONS ###
@doc """
Calculates a percentage with specified precision.
## Parameters
- `value`: The value to calculate percentage for
- `total`: The total value (100%)
- `precision`: Number of decimal places (default: 2)
## Examples
iex> NumberF.percentage(25, 100)
25.0
iex> NumberF.percentage(1, 3, 2)
33.33
"""
def percentage(value, total, precision \\ 2) do
result = value / total * 100
Float.round(result, precision)
end
@doc """
Rounds a number to the nearest specified value.
## Parameters
- `value`: The number to round
- `nearest`: The nearest value to round to (default: 1.0)
## Examples
iex> NumberF.round_to_nearest(12.3)
12.0
iex> NumberF.round_to_nearest(12.3, 5)
10.0
iex> NumberF.round_to_nearest(12.3, 0.5)
12.5
"""
def round_to_nearest(value, nearest \\ 1.0) do
round(value / nearest) * nearest
end
@doc """
Checks if a number is within a specified range (inclusive).
## Parameters
- `value`: The number to check
- `min`: The minimum value of the range
- `max`: The maximum value of the range
## Examples
iex> NumberF.in_range?(5, 1, 10)
true
iex> NumberF.in_range?(15, 1, 10)
false
"""
def in_range?(value, min, max) when is_number(value) and is_number(min) and is_number(max) do
value >= min and value <= max
end
### FINANCIAL FUNCTIONS ###
@doc """
Calculates simple interest based on principal, rate and time.
## Parameters
- `principal`: The principal amount
- `rate`: The annual interest rate as a decimal (e.g., 0.05 for 5%)
- `time`: The time period in years
## Examples
iex> NumberF.simple_interest(1000, 0.05, 2)
100.0
"""
def simple_interest(principal, rate, time)
when is_number(principal) and is_number(rate) and is_number(time) do
principal * rate * time
end
@doc """
Calculates compound interest with optional compounding frequency.
## Features
- Support for different compounding frequencies (annual, semi-annual, quarterly, monthly, etc.)
- Precise calculations using Elixir's floating-point operations
- Results rounded to 2 decimal places by default
- Guard clauses ensure valid inputs
## Parameters
- `principal`: The principal amount
- `rate`: The annual interest rate as a decimal (e.g., 0.05 for 5%)
- `time`: The time period in years
- `frequency`: Number of times interest is compounded per year (default: 1)
## Examples
iex> NumberF.compound_interest(1000, 0.05, 2)
102.5 # Annual compounding (1 time per year)
iex> NumberF.compound_interest(1000, 0.05, 2, 12)
104.94 # Monthly compounding (12 times per year)
iex> NumberF.compound_interest(10000, 0.08, 5, 4)
4693.28 # Quarterly compounding (4 times per year)
## Formula
The compound interest is calculated using the formula:
```
A = P(1 + r/n)^(nt) - P
```
Where:
- A = Interest amount
- P = Principal
- r = Annual interest rate (decimal)
- n = Compounding frequency
- t = Time in years
## Common Use Cases
- Investment calculations
- Loan and mortgage analysis
- Retirement planning
- Financial education tools
## Related Functions
- `NumberF.simple_interest/3`: Calculate simple interest
- `NumberF.calculate_emi/3`: Calculate equated monthly installments
- `NumberF.Tax.calculate_capital_gains_tax/3`: Calculate capital gains tax
See also: `NumberF.Financial` for more financial calculations.
"""
def compound_interest(principal, rate, time, frequency \\ 1)
when is_number(principal) and is_number(rate) and is_number(time) and is_number(frequency) do
result = principal * :math.pow(1 + rate / frequency, frequency * time) - principal
Float.round(result, 2)
end
@doc """
Calculates Equated Monthly Installment (EMI) for loans.
## Parameters
- `principal`: The loan amount
- `rate`: The annual interest rate as a decimal (e.g., 0.05 for 5%)
- `term_months`: The loan term in months
## Examples
iex> NumberF.calculate_emi(100000, 0.10, 12)
8791.59
"""
def calculate_emi(principal, rate, term_months)
when is_number(principal) and is_number(rate) and is_number(term_months) and term_months > 0 do
monthly_rate = rate / 12
emi =
principal * monthly_rate * :math.pow(1 + monthly_rate, term_months) /
(:math.pow(1 + monthly_rate, term_months) - 1)
Float.round(emi, 2)
end
@doc """
Converts an amount between currencies based on exchange rates.
## Parameters
- `amount`: The amount to convert
- `from_rate`: The exchange rate of the source currency
- `to_rate`: The exchange rate of the target currency
## Examples
iex> NumberF.convert_currency(100, 1, 1.1)
110.0
"""
def convert_currency(amount, from_rate, to_rate)
when is_number(amount) and is_number(from_rate) and is_number(to_rate) and from_rate > 0 and
to_rate > 0 do
amount * to_rate / from_rate
end
### STATISTICAL FUNCTIONS ###
@doc """
Calculates the arithmetic mean of a list of numbers.
## Features
- Handles lists of any size
- Returns nil for empty lists
- Works with integers and floating-point numbers
- Accurately calculates average using sum and count
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.mean([1, 2, 3, 4, 5])
3.0
iex> NumberF.mean([1.5, 2.5, 3.5])
2.5
iex> NumberF.mean([42])
42.0
iex> NumberF.mean([])
nil
## Mathematical Definition
The arithmetic mean is the sum of all values divided by the number of values.
## Common Use Cases
- Data analysis and statistics
- Financial analysis (average returns, costs, etc.)
- Scientific calculations
- Performance metrics
## Related Functions
- `NumberF.median/1`: Calculate the median value
- `NumberF.mode/1`: Find the most frequent value(s)
- `NumberF.standard_deviation/1`: Calculate standard deviation
- `NumberF.variance/1`: Calculate variance
See also: `NumberF.Statistics` for more statistical functions.
"""
def mean([]), do: nil
def mean(numbers) when is_list(numbers) do
total = Enum.sum(numbers)
count = length(numbers)
total / count
end
@doc """
Finds the median value from a list of numbers.
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.median([1, 3, 5, 7, 9])
5
iex> NumberF.median([1, 3, 5, 7])
4.0
"""
def median([]), do: nil
def median(numbers) when is_list(numbers) do
sorted = Enum.sort(numbers)
count = length(sorted)
if rem(count, 2) == 0 do
# Even number of elements
middle_idx = div(count, 2)
(Enum.at(sorted, middle_idx - 1) + Enum.at(sorted, middle_idx)) / 2
else
# Odd number of elements
Enum.at(sorted, div(count, 2))
end
end
@doc """
Finds the most frequently occurring value(s) in a list.
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.mode([1, 2, 2, 3, 3, 3, 4])
[3]
iex> NumberF.mode([1, 1, 2, 2, 3])
[1, 2]
"""
def mode([]), do: []
def mode(numbers) when is_list(numbers) do
frequencies =
Enum.reduce(numbers, %{}, fn num, acc ->
Map.update(acc, num, 1, &(&1 + 1))
end)
{_count, max_freq} = Enum.max_by(frequencies, fn {_num, count} -> count end)
Enum.filter(frequencies, fn {_num, count} -> count == max_freq end)
|> Enum.map(fn {num, _count} -> num end)
|> Enum.sort()
end
@doc """
Calculates standard deviation of a dataset.
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.standard_deviation([2, 4, 4, 4, 5, 5, 7, 9])
2.0
"""
def standard_deviation([]), do: nil
def standard_deviation([_single]), do: 0.0
def standard_deviation(numbers) when is_list(numbers) do
avg = mean(numbers)
variance =
Enum.reduce(numbers, 0, fn num, sum ->
sum + :math.pow(num - avg, 2)
end) / (length(numbers) - 1)
:math.sqrt(variance)
end
### NUMBER VALIDATION ###
@doc """
Checks if a string is a valid number format.
## Parameters
- `str`: The string to check
## Examples
iex> NumberF.is_valid_number?("123")
true
iex> NumberF.is_valid_number?("123.45")
true
iex> NumberF.is_valid_number?("abc")
false
"""
def is_valid_number?(str) when is_binary(str) do
case Float.parse(str) do
{_num, ""} -> true
_ -> false
end
end
def is_valid_number?(_), do: false
@doc """
Checks if a string is a valid integer.
## Parameters
- `str`: The string to check
## Examples
iex> NumberF.is_valid_integer?("123")
true
iex> NumberF.is_valid_integer?("123.45")
false
"""
def is_valid_integer?(str) when is_binary(str) do
case Integer.parse(str) do
{_num, ""} -> true
_ -> false
end
end
def is_valid_integer?(_), do: false
@doc """
Validates credit card numbers using the Luhn algorithm.
## Parameters
- `number`: The credit card number as a string
- `type`: Card type to validate against (default: :any)
- Options: :any, :visa, :mastercard, :amex, :discover
## Examples
iex> NumberF.is_valid_credit_card?("4111111111111111")
true
iex> NumberF.is_valid_credit_card?("4111111111111112")
false
"""
def is_valid_credit_card?(number, type \\ :any)
def is_valid_credit_card?(number, type) when is_binary(number) do
# Remove any spaces or dashes
cleaned = String.replace(number, ~r/[\s-]/, "")
# Check if all characters are digits
if Regex.match?(~r/^\d+$/, cleaned) do
# Validate card type by pattern
card_valid? =
case type do
:visa -> Regex.match?(~r/^4[0-9]{12}(?:[0-9]{3})?$/, cleaned)
:mastercard -> Regex.match?(~r/^5[1-5][0-9]{14}$/, cleaned)
:amex -> Regex.match?(~r/^3[47][0-9]{13}$/, cleaned)
:discover -> Regex.match?(~r/^6(?:011|5[0-9]{2})[0-9]{12}$/, cleaned)
:any -> true
end
if card_valid? do
luhn_valid?(cleaned)
else
false
end
else
false
end
end
def is_valid_credit_card?(_, _), do: false
# Luhn algorithm implementation
defp luhn_valid?(number) do
digits = String.graphemes(number) |> Enum.map(&String.to_integer/1)
# Double every second digit from right to left
doubled =
digits
|> Enum.reverse()
|> Enum.with_index()
|> Enum.map(fn {digit, i} ->
if rem(i, 2) == 1 do
case digit * 2 do
n when n > 9 -> n - 9
n -> n
end
else
digit
end
end)
|> Enum.sum()
rem(doubled, 10) == 0
end
### FORMATTING ENHANCEMENTS ###
@doc """
Formats phone numbers based on country code.
## Parameters
- `number`: The phone number as a string
- `country_code`: The country code (default: "ZM" for Zambia)
## Examples
iex> NumberF.format_phone("260977123456", "ZM")
"+260 97 712 3456"
iex> NumberF.format_phone("14155552671", "US")
"+1 (415) 555-2671"
"""
def format_phone(number, country_code \\ "ZM") when is_binary(number) do
# Remove any non-digit characters
digits = String.replace(number, ~r/\D/, "")
case country_code do
"ZM" ->
if String.starts_with?(digits, "260") do
"+260 " <>
String.slice(digits, 3, 2) <>
" " <>
String.slice(digits, 5, 3) <> " " <> String.slice(digits, 8, 4)
else
"0" <>
String.slice(digits, 0, 2) <>
" " <>
String.slice(digits, 2, 3) <> " " <> String.slice(digits, 5, 4)
end
"US" ->
if String.starts_with?(digits, "1") and String.length(digits) == 11 do
"+1 (" <>
String.slice(digits, 1, 3) <>
") " <>
String.slice(digits, 4, 3) <> "-" <> String.slice(digits, 7, 4)
else
"(" <>
String.slice(digits, 0, 3) <>
") " <>
String.slice(digits, 3, 3) <> "-" <> String.slice(digits, 6, 4)
end
"UK" ->
if String.starts_with?(digits, "44") do
"+44 " <>
String.slice(digits, 2, 2) <>
" " <>
String.slice(digits, 4, 4) <> " " <> String.slice(digits, 8, 4)
else
"0" <>
String.slice(digits, 0, 2) <>
" " <>
String.slice(digits, 2, 4) <> " " <> String.slice(digits, 6, 4)
end
_ ->
# Generic international format
"+" <> digits
end
end
@doc """
Formats large numbers as K, M, B (e.g., 1.2K, 3.4M).
## Parameters
- `number`: The number to abbreviate
- `precision`: Number of decimal places (default: 1)
## Examples
iex> NumberF.abbreviate_number(1234)
"1.2K"
iex> NumberF.abbreviate_number(1234567)
"1.2M"
iex> NumberF.abbreviate_number(1234567890)
"1.2B"
"""
def abbreviate_number(number, precision \\ 1) when is_number(number) do
cond do
number >= 1_000_000_000 ->
"#{Float.round(number / 1_000_000_000, precision)}B"
number >= 1_000_000 ->
"#{Float.round(number / 1_000_000, precision)}M"
number >= 1_000 ->
"#{Float.round(number / 1_000, precision)}K"
true ->
"#{number}"
end
end
@doc """
Converts numbers to ordinals (1st, 2nd, 3rd, etc.).
## Parameters
- `number`: The number to convert
## Examples
iex> NumberF.ordinal(1)
"1st"
iex> NumberF.ordinal(2)
"2nd"
iex> NumberF.ordinal(3)
"3rd"
iex> NumberF.ordinal(4)
"4th"
"""
def ordinal(number) when is_integer(number) and number > 0 do
suffix =
cond do
rem(number, 100) in [11, 12, 13] -> "th"
rem(number, 10) == 1 -> "st"
rem(number, 10) == 2 -> "nd"
rem(number, 10) == 3 -> "rd"
true -> "th"
end
"#{number}#{suffix}"
end
@doc """
Converts Arabic numbers to Roman numerals.
## Parameters
- `number`: The number to convert (1-3999)
## Examples
iex> NumberF.to_roman(4)
"IV"
iex> NumberF.to_roman(42)
"XLII"
iex> NumberF.to_roman(1999)
"MCMXCIX"
"""
def to_roman(number) when is_integer(number) and number > 0 and number < 4000 do
roman_map = [
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"}
]
{_num, result} =
Enum.reduce(roman_map, {number, ""}, fn {val, rom}, {num, acc} ->
count = div(num, val)
remainder = rem(num, val)
{remainder, acc <> String.duplicate(rom, count)}
end)
result
end
@doc """
Converts Roman numerals to Arabic numbers.
## Parameters
- `roman`: The Roman numeral string
## Examples
iex> NumberF.from_roman("IV")
4
iex> NumberF.from_roman("XLII")
42
iex> NumberF.from_roman("MCMXCIX")
1999
"""
def from_roman(roman) when is_binary(roman) do
chars = String.graphemes(roman)
values = %{
"I" => 1,
"V" => 5,
"X" => 10,
"L" => 50,
"C" => 100,
"D" => 500,
"M" => 1000
}
{result, _} =
Enum.reduce(chars, {0, 0}, fn char, {sum, prev} ->
current = Map.get(values, char, 0)
# If current value is greater than previous, subtract twice the previous
# (we already added it once in the previous iteration)
if current > prev and prev > 0 do
{sum - prev * 2 + current, current}
else
{sum + current, current}
end
end)
result
end
### DATE-BASED CALCULATIONS ###
@doc """
Calculates age based on birth date.
## Parameters
- `birth_date`: Birth date as Date struct
## Examples
iex> birth_date = ~D[1990-01-15]
iex> age = NumberF.calculate_age(birth_date)
# Returns the current age based on today's date
"""
def calculate_age(birth_date) do
today = Date.utc_today()
years = today.year - birth_date.year
# Adjust if birthday hasn't occurred yet this year
if Date.compare(
%{today | year: today.year - years},
birth_date
) == :lt do
years - 1
else
years
end
end
@doc """
Calculates payment due date based on invoice date and terms.
## Parameters
- `invoice_date`: The invoice date as Date struct
- `terms_days`: Payment terms in days (default: 30)
## Examples
iex> invoice_date = ~D[2023-01-15]
iex> NumberF.payment_due_date(invoice_date)
~D[2023-02-14]
iex> invoice_date = ~D[2023-01-15]
iex> NumberF.payment_due_date(invoice_date, 45)
~D[2023-03-01]
"""
def payment_due_date(invoice_date, terms_days \\ 30)
def payment_due_date(invoice_date, terms_days) do
Date.add(invoice_date, terms_days)
end
### UNIT CONVERSION FUNCTIONS ###
@doc """
Converts inches to centimeters.
## Parameters
- `inches`: The length in inches
## Examples
iex> NumberF.inches_to_cm(10)
25.4
iex> NumberF.inches_to_cm(3.5)
8.89
"""
def inches_to_cm(inches), do: NumberF.Metrics.inches_to_cm(inches)
@doc """
Converts centimeters to inches.
## Parameters
- `cm`: The length in centimeters
## Examples
iex> NumberF.cm_to_inches(25.4)
10.0
"""
def cm_to_inches(cm), do: NumberF.Metrics.cm_to_inches(cm)
@doc """
Converts miles to kilometers.
## Parameters
- `miles`: The distance in miles
## Examples
iex> NumberF.miles_to_km(10)
16.09
"""
def miles_to_km(miles), do: NumberF.Metrics.miles_to_km(miles)
@doc """
Converts kilometers to miles.
## Parameters
- `km`: The distance in kilometers
## Examples
iex> NumberF.km_to_miles(16.09)
10.0
"""
def km_to_miles(km), do: NumberF.Metrics.km_to_miles(km)
@doc """
Converts Fahrenheit to Celsius.
## Parameters
- `fahrenheit`: The temperature in Fahrenheit
## Examples
iex> NumberF.fahrenheit_to_celsius(32)
0.0
iex> NumberF.fahrenheit_to_celsius(212)
100.0
"""
def fahrenheit_to_celsius(fahrenheit), do: NumberF.Metrics.fahrenheit_to_celsius(fahrenheit)
@doc """
Converts Celsius to Fahrenheit.
## Parameters
- `celsius`: The temperature in Celsius
## Examples
iex> NumberF.celsius_to_fahrenheit(0)
32.0
iex> NumberF.celsius_to_fahrenheit(100)
212.0
"""
def celsius_to_fahrenheit(celsius), do: NumberF.Metrics.celsius_to_fahrenheit(celsius)
### PRECISION HANDLING FUNCTIONS ###
@doc """
Rounds a number to a specified number of decimal places.
## Parameters
- `number`: The number to round
- `precision`: Number of decimal places (default: 2)
## Examples
iex> NumberF.round_with_precision(3.14159, 2)
3.14
iex> NumberF.round_with_precision(3.14159, 4)
3.1416
"""
def round_with_precision(number, precision \\ 2),
do: NumberF.Precision.round_with_precision(number, precision)
@doc """
Rounds a number using banker's rounding (round to even).
This is more statistically unbiased than standard rounding.
## Parameters
- `number`: The number to round
- `precision`: Number of decimal places (default: 2)
## Examples
iex> NumberF.bankers_round(2.5, 0)
2.0
iex> NumberF.bankers_round(3.5, 0)
4.0
"""
def bankers_round(number, precision \\ 2),
do: NumberF.Precision.bankers_round(number, precision)
@doc """
Checks if two floating point numbers are approximately equal.
## Parameters
- `a`: First number
- `b`: Second number
- `epsilon`: Maximum allowed difference (default: 1.0e-10)
## Examples
iex> NumberF.approximately_equal(0.1 + 0.2, 0.3)
true
iex> NumberF.approximately_equal(0.1, 0.2)
false
"""
def approximately_equal(a, b, epsilon \\ 1.0e-10),
do: NumberF.Precision.approximately_equal(a, b, epsilon)
### TAX CALCULATION FUNCTIONS ###
@doc """
Calculates Value Added Tax (VAT) for a given amount and rate.
## Parameters
- `amount`: The amount before tax
- `rate`: The VAT rate as a decimal (e.g., 0.2 for 20%)
- `included`: Whether the amount already includes VAT (default: false)
## Examples
iex> NumberF.calculate_vat(100, 0.2)
%{net: 100.0, vat: 20.0, gross: 120.0}
iex> NumberF.calculate_vat(120, 0.2, true)
%{net: 100.0, vat: 20.0, gross: 120.0}
"""
def calculate_vat(amount, rate, included \\ false),
do: NumberF.Tax.calculate_vat(amount, rate, included)
@doc """
Calculates sales tax for a given amount and rate.
## Parameters
- `amount`: The amount before tax
- `rate`: The sales tax rate as a decimal (e.g., 0.06 for 6%)
- `options`: Additional options
- `:round_to`: Round the tax amount to the nearest value (default: 0.01)
## Examples
iex> NumberF.calculate_sales_tax(100, 0.06)
%{subtotal: 100.0, tax: 6.0, total: 106.0}
"""
def calculate_sales_tax(amount, rate, options \\ []),
do: NumberF.Tax.calculate_sales_tax(amount, rate, options)
@doc """
Returns common VAT rates for different countries.
## Examples
iex> NumberF.vat_rates()["UK"]
0.2
iex> NumberF.vat_rates()["Germany"]
0.19
"""
def vat_rates, do: NumberF.Tax.vat_rates()
### INTERNATIONALIZATION FUNCTIONS ###
@doc """
Formats a number according to locale-specific settings.
## Parameters
- `number`: The number to format
- `locale`: The locale code (e.g., "en-US", "fr-FR")
- `options`: Additional formatting options
- `:precision`: Number of decimal places (default: 2)
## Examples
iex> NumberF.format_number(1234567.89, "en-US")
"1,234,567.89"
iex> NumberF.format_number(1234567.89, "fr-FR")
"1 234 567,89"
"""
def format_number(number, locale, options \\ []),
do: NumberF.I18n.format_number(number, locale, options)
@doc """
Formats a number as currency according to locale-specific settings.
## Features
- Supports 100+ international locale formats
- Proper positioning of currency symbols
- Correct thousands and decimal separators for each locale
- Configurable precision and currency code
## Parameters
- `number`: The number to format
- `locale`: The locale code (e.g., "en-US", "fr-FR")
- `options`: Additional formatting options
- `:precision`: Number of decimal places (default: 2)
- `:currency_code`: ISO currency code to override the locale default
- `:symbol`: Whether to include the currency symbol (default: true)
## Examples
iex> NumberF.format_currency(1234.56, "en-US")
"$1,234.56"
iex> NumberF.format_currency(1234.56, "fr-FR")
"1 234,56 €"
iex> NumberF.format_currency(1234.56, "de-DE", currency_code: "USD")
"1.234,56 $"
iex> NumberF.format_currency(1234.56, "en-US", symbol: false)
"1,234.56"
## Supported Locales
This function supports all major world locales including but not limited to:
en-US, en-GB, fr-FR, de-DE, es-ES, it-IT, ja-JP, zh-CN, ru-RU, pt-BR, and many more.
## Common Use Cases
- Multi-language applications
- Financial applications with international users
- E-commerce with global customers
- Travel and currency conversion applications
## Related Functions
- `NumberF.format_number/3`: Format without currency symbols
- `NumberF.get_locale_settings/1`: Get formatting rules for a locale
- `NumberF.spell_number/3`: Spell out numbers in different languages
See also: `NumberF.I18n` for more internationalization functions.
"""
def format_currency(number, locale, options \\ []),
do: NumberF.I18n.format_currency(number, locale, options)
@doc """
Spells out a number in the specified language.
## Parameters
- `number`: The number to spell out
- `language`: The language code (e.g., "en", "fr")
- `options`: Additional options
- `:capitalize`: Whether to capitalize the first letter (default: true)
- `:currency`: Whether to add currency names (default: false)
- `:currency_code`: ISO currency code (default: nil)
## Examples
iex> NumberF.spell_number(42, "en")
"Forty-two"
iex> NumberF.spell_number(42, "fr")
"Quarante-deux"
"""
def spell_number(number, language, options \\ []),
do: NumberF.I18n.spell_number(number, language, options)
### CURRENCY UTILITY FUNCTIONS ###
@doc """
Returns a map of currency information with ISO codes, symbols, and formatting details.
## Examples
iex> NumberF.currency_data()["USD"]
%{
name: "US Dollar",
symbol: "$",
symbol_first: true,
symbol_space: false,
decimal_places: 2,
thousands_separator: ",",
decimal_separator: "."
}
"""
def currency_data, do: NumberF.Currencies.currency_data()
@doc """
Gets currency details for a specific currency code.
## Parameters
- `currency_code`: ISO currency code (e.g., "USD", "EUR")
## Examples
iex> NumberF.get_currency("USD")
%{
name: "US Dollar",
symbol: "$",
symbol_first: true,
symbol_space: false,
decimal_places: 2,
thousands_separator: ",",
decimal_separator: "."
}
"""
def get_currency(currency_code), do: NumberF.Currencies.get_currency(currency_code)
@doc """
Formats a currency with the specified currency code's rules.
## Parameters
- `number`: The number to format
- `currency_code`: ISO currency code (e.g., "USD", "EUR")
- `options`: Additional options (override currency defaults)
## Examples
iex> NumberF.format_with_currency(1234.56, "USD")
"$1,234.56"
iex> NumberF.format_with_currency(1234.56, "EUR")
"1.234,56 €"
"""
def format_with_currency(number, currency_code, options \\ []),
do: NumberF.Currencies.format(number, currency_code, options)
### REGISTRY AND INFORMATION ###
@doc """
Returns information about all NumberF modules.
## Examples
iex> NumberF.modules() |> Enum.map(& &1.name) |> Enum.take(3)
["NumberF", "NumberF.Registry", "NumberF.Currency"]
"""
def modules, do: NumberF.Registry.modules()
@doc """
Returns information about NumberF modules of a specific type.
## Parameters
- `type`: The module type (e.g., :formatting, :calculation)
## Examples
iex> NumberF.modules_by_type(:formatting) |> Enum.map(& &1.name)
["NumberF.Currency", "NumberF.CustomFormatter", "NumberF.Formatter", "NumberF.Currencies"]
"""
def modules_by_type(type), do: NumberF.Registry.modules_by_type(type)
@doc """
Returns all function categories available in NumberF.
## Examples
iex> NumberF.function_categories() |> Enum.map(& &1.name) |> Enum.take(3)
["Formatting", "Conversion", "Generation"]
"""
def function_categories, do: NumberF.Registry.function_categories()
@doc """
Returns a markdown documentation of all NumberF modules and functions.
## Examples
iex> markdown = NumberF.documentation()
iex> String.starts_with?(markdown, "# NumberF Library")
true
"""
def documentation, do: NumberF.Registry.to_markdown()
### EXTENDED MATH OPERATIONS ###
@doc """
Calculates the factorial of a non-negative integer.
## Parameters
- `n`: The non-negative integer
## Examples
iex> NumberF.factorial(5)
120
iex> NumberF.factorial(0)
1
"""
def factorial(n) when n >= 0, do: NumberF.Calculations.factorial(n)
@doc """
Calculates combinations (n choose k).
## Parameters
- `n`: The total number of items
- `k`: The number of items to choose
## Examples
iex> NumberF.combinations(5, 2)
10.0
iex> NumberF.combinations(10, 3)
120.0
"""
def combinations(n, k) when k <= n, do: NumberF.Calculations.combinations(n, k)
@doc """
Calculates the Greatest Common Divisor (GCD) of two integers.
## Parameters
- `a`: First integer
- `b`: Second integer
## Examples
iex> NumberF.gcd(48, 18)
6
iex> NumberF.gcd(7, 13)
1
"""
def gcd(a, b), do: NumberF.Calculations.gcd(a, b)
@doc """
Calculates the Least Common Multiple (LCM) of two integers.
## Parameters
- `a`: First integer
- `b`: Second integer
## Examples
iex> NumberF.lcm(4, 6)
12
iex> NumberF.lcm(21, 6)
42
"""
def lcm(a, b), do: NumberF.Calculations.lcm(a, b)
@doc """
Checks if a number is prime.
## Parameters
- `n`: The number to check
## Examples
iex> NumberF.is_prime?(7)
true
iex> NumberF.is_prime?(6)
false
"""
def is_prime?(n), do: NumberF.Calculations.is_prime?(n)
@doc """
Performs a linear interpolation between two points.
## Parameters
- `x`: The x value to interpolate at
- `x0`: The x coordinate of the first point
- `y0`: The y coordinate of the first point
- `x1`: The x coordinate of the second point
- `y1`: The y coordinate of the second point
## Examples
iex> NumberF.interpolate(2.5, 2, 10, 3, 20)
15.0
"""
def interpolate(x, x0, y0, x1, y1), do: NumberF.Calculations.interpolate(x, x0, y0, x1, y1)
@doc """
Converts degrees to radians.
## Parameters
- `degrees`: The angle in degrees
## Examples
iex> NumberF.to_radians(180)
3.141592653589793
"""
def to_radians(degrees), do: NumberF.Calculations.to_radians(degrees)
@doc """
Converts radians to degrees.
## Parameters
- `radians`: The angle in radians
## Examples
iex> NumberF.to_degrees(3.14159)
180.0
"""
def to_degrees(radians), do: NumberF.Calculations.to_degrees(radians)
### EXTENDED STATISTICS FUNCTIONS ###
@doc """
Calculates the variance of a dataset.
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.variance([2, 4, 4, 4, 5, 5, 7, 9])
4.0
"""
def variance(numbers), do: NumberF.Statistics.variance(numbers)
@doc """
Calculates the range (difference between max and min values) of a dataset.
## Parameters
- `numbers`: A list of numbers
## Examples
iex> NumberF.range([2, 4, 4, 4, 5, 5, 7, 9])
7.0
"""
def range(numbers), do: NumberF.Statistics.range(numbers)
### EXTENDED DATE FUNCTIONS ###
@doc """
Calculates the number of days between two dates.
## Parameters
- `date1`: The first date
- `date2`: The second date
## Examples
iex> NumberF.days_between(~D[2023-01-01], ~D[2023-01-10])
9
"""
def days_between(date1, date2), do: NumberF.DateCalculations.days_between(date1, date2)
@doc """
Calculates the number of business days between two dates.
## Parameters
- `date1`: The first date
- `date2`: The second date
## Examples
iex> NumberF.business_days_between(~D[2023-01-02], ~D[2023-01-08])
5
"""
def business_days_between(date1, date2),
do: NumberF.DateCalculations.business_days_between(date1, date2)
@doc """
Determines if a date is a business day.
## Parameters
- `date`: The date to check
## Examples
iex> NumberF.is_business_day?(~D[2023-01-02])
true
iex> NumberF.is_business_day?(~D[2023-01-01])
false
"""
def is_business_day?(date), do: NumberF.DateCalculations.is_business_day?(date)
@doc """
Returns all modules and their functions as a reference.
This function provides a comprehensive overview of all available functionality
in the NumberF library, organized by module.
## Examples
iex> ref = NumberF.reference()
iex> is_map(ref)
true
"""
def reference do
%{
description:
"A comprehensive utility library for number formatting, conversion, and manipulation in Elixir",
version: "0.2.0",
modules: NumberF.Registry.modules(),
function_categories: NumberF.Registry.function_categories()
}
end
end