Lua - Numbers

Overview

Estimated time: 25–30 minutes

Numbers are fundamental to programming and Lua provides robust numeric capabilities. Lua has a unified number type that can represent both integers and floating-point numbers, along with a comprehensive math library for advanced mathematical operations.

Learning Objectives

  • Understand Lua's number system and number types
  • Master basic arithmetic and mathematical operations
  • Explore the math library and advanced functions
  • Learn number formatting and conversion techniques
  • Apply numerical computing in practical scenarios

Prerequisites

  • Understanding of Lua operators and expressions
  • Knowledge of variables and functions

Lua Number System

Lua has a single number type that can represent both integers and floating-point numbers:

-- Integer literals
local integer1 = 42
local integer2 = -17
local zero = 0

-- Floating-point literals
local float1 = 3.14159
local float2 = -2.5
local scientific = 1.23e-4  -- Scientific notation
local scientific2 = 5.67E+2  -- 567.0

-- Different number bases (Lua 5.2+)
local decimal = 255
local hexadecimal = 0xFF    -- Same as 255
local binary = 0b11111111   -- Same as 255 (Lua 5.4+)

print("Integer:", integer1, type(integer1))
print("Float:", float1, type(float1))
print("Scientific:", scientific, type(scientific))
print("Hex 0xFF:", hexadecimal)
print("Binary 0b11111111:", binary)

-- Check if a number is an integer (Lua 5.3+)
print("Is 42 an integer?", math.type(42))
print("Is 3.14 an integer?", math.type(3.14))
print("Is 5.0 an integer?", math.type(5.0))

Expected Output:

Integer:	42	number
Float:	3.14159	number
Scientific:	0.000123	number
Hex 0xFF:	255
Binary 0b11111111:	255
Is 42 an integer?	integer
Is 3.14 an integer?	float
Is 5.0 an integer?	float

Basic Arithmetic Operations

Lua supports all standard arithmetic operations:

local a = 15
local b = 4

print("Basic arithmetic operations:")
print("a + b =", a + b)    -- Addition: 19
print("a - b =", a - b)    -- Subtraction: 11
print("a * b =", a * b)    -- Multiplication: 60
print("a / b =", a / b)    -- Division: 3.75
print("a // b =", a // b)  -- Floor division: 3
print("a % b =", a % b)    -- Modulo: 3
print("a ^ b =", a ^ b)    -- Exponentiation: 50625

-- Unary operations
print("-a =", -a)          -- Unary minus: -15

-- Mixed integer and float operations
local int_val = 10
local float_val = 3.5
print("Mixed operations:")
print("10 + 3.5 =", int_val + float_val)    -- 13.5
print("10 * 3.5 =", int_val * float_val)    -- 35.0
print("10 / 3.5 =", int_val / float_val)    -- 2.857...

Expected Output:

Basic arithmetic operations:
a + b =	19
a - b =	11
a * b =	60
a / b =	3.75
a // b =	3
a % b =	3
a ^ b =	50625
-a =	-15
Mixed operations:
10 + 3.5 =	13.5
10 * 3.5 =	35.0
10 / 3.5 =	2.8571428571429

Number Conversion

Converting between numbers and other types:

-- String to number conversion
local str_num = "123"
local str_float = "45.67"
local str_invalid = "abc"

print("String to number conversions:")
print("tonumber('123'):", tonumber(str_num))
print("tonumber('45.67'):", tonumber(str_float))
print("tonumber('abc'):", tonumber(str_invalid))  -- Returns nil

-- With different bases
print("tonumber('FF', 16):", tonumber("FF", 16))   -- Hex to decimal
print("tonumber('1010', 2):", tonumber("1010", 2)) -- Binary to decimal
print("tonumber('77', 8):", tonumber("77", 8))     -- Octal to decimal

-- Number to string conversion
local num = 42.5
print("Number to string:")
print("tostring(42.5):", tostring(num))
print("Concatenation:", "Value: " .. num)

-- Safe conversion function
local function safe_tonumber(str, default)
    local num = tonumber(str)
    return num or default or 0
end

print("Safe conversions:")
print("safe_tonumber('123'):", safe_tonumber("123"))
print("safe_tonumber('abc', -1):", safe_tonumber("abc", -1))
print("safe_tonumber('xyz'):", safe_tonumber("xyz"))

Expected Output:

String to number conversions:
tonumber('123'):	123
tonumber('45.67'):	45.67
tonumber('abc'):	nil
tonumber('FF', 16):	255
tonumber('1010', 2):	10
tonumber('77', 8):	63
Number to string:
tostring(42.5):	42.5
Concatenation:	Value: 42.5
Safe conversions:
safe_tonumber('123'):	123
safe_tonumber('abc', -1):	-1
safe_tonumber('xyz'):	0

Math Library

Lua's math library provides advanced mathematical functions:

Constants and Basic Functions

-- Mathematical constants
print("Math constants:")
print("π (pi):", math.pi)
print("Infinity:", math.huge)
print("Smallest normal positive double:", math.mininteger)
print("Largest representable integer:", math.maxinteger)

-- Basic math functions
local x = -3.7
print("\nBasic functions for x =", x)
print("Absolute value:", math.abs(x))
print("Floor (round down):", math.floor(x))
print("Ceiling (round up):", math.ceil(x))
print("Sign:", x > 0 and 1 or x < 0 and -1 or 0)

-- Rounding to nearest integer
local function round(num)
    return math.floor(num + 0.5)
end

local numbers = {3.2, 3.7, -2.3, -2.8}
print("\nRounding examples:")
for _, num in ipairs(numbers) do
    print(string.format("%.1f -> floor: %d, ceil: %d, round: %d", 
          num, math.floor(num), math.ceil(num), round(num)))
end

Expected Output:

Math constants:
π (pi):	3.1415926535898
Infinity:	inf
Smallest normal positive double:	-9223372036854775808
Largest representable integer:	9223372036854775807

Basic functions for x =	-3.7
Absolute value:	3.7
Floor (round down):	-4
Ceiling (round up):	-3
Sign:	-1

Rounding examples:
3.2 -> floor: 3, ceil: 4, round: 3
3.7 -> floor: 3, ceil: 4, round: 4
-2.3 -> floor: -3, ceil: -2, round: -2
-2.8 -> floor: -3, ceil: -2, round: -3

Power and Root Functions

-- Power and exponential functions
local base = 2
local exponent = 3

print("Power functions:")
print("2^3 (using ^):", base ^ exponent)
print("2^3 (using math.pow):", math.pow(base, exponent))  -- Deprecated in Lua 5.3+
print("Square root of 16:", math.sqrt(16))
print("Cube root of 27:", 27 ^ (1/3))

-- Exponential and logarithm functions
local e_value = math.exp(1)  -- e^1
print("\nExponential and logarithms:")
print("e (Euler's number):", e_value)
print("e^2:", math.exp(2))
print("Natural log of e:", math.log(e_value))
print("Log base 10 of 100:", math.log(100, 10))  -- Lua 5.2+
print("Log base 2 of 8:", math.log(8, 2))

-- Practical example: compound interest
local function compound_interest(principal, rate, time, compounds_per_year)
    compounds_per_year = compounds_per_year or 1
    return principal * ((1 + rate / compounds_per_year) ^ (compounds_per_year * time))
end

local principal = 1000
local annual_rate = 0.05  -- 5%
local years = 10

print("\nCompound interest calculation:")
print(string.format("Principal: $%.2f", principal))
print(string.format("Annual rate: %.1f%%", annual_rate * 100))
print(string.format("Time: %d years", years))
print(string.format("Final amount: $%.2f", compound_interest(principal, annual_rate, years)))

Expected Output:

Power functions:
2^3 (using ^):	8
2^3 (using math.pow):	8
Square root of 16:	4
Cube root of 27:	3

Exponential and logarithms:
e (Euler's number):	2.718281828459
e^2:	7.3890560989307
Natural log of e:	1
Log base 10 of 100:	2
Log base 2 of 8:	3

Compound interest calculation:
Principal: $1000.00
Annual rate: 5.0%
Time: 10 years
Final amount: $1628.89

Trigonometric Functions

-- Trigonometric functions (work with radians)
local degrees = 45
local radians = math.rad(degrees)  -- Convert degrees to radians

print("Trigonometric functions:")
print(string.format("45° = %.4f radians", radians))
print(string.format("sin(45°) = %.4f", math.sin(radians)))
print(string.format("cos(45°) = %.4f", math.cos(radians)))
print(string.format("tan(45°) = %.4f", math.tan(radians)))

-- Inverse trigonometric functions
local value = 0.5
print(string.format("\nInverse trig functions for %.1f:", value))
print(string.format("arcsin(%.1f) = %.2f° ", value, math.deg(math.asin(value))))
print(string.format("arccos(%.1f) = %.2f°", value, math.deg(math.acos(value))))
print(string.format("arctan(%.1f) = %.2f°", value, math.deg(math.atan(value))))

-- Two-argument arctangent
local y, x = 1, 1
print(string.format("atan2(%d, %d) = %.2f°", y, x, math.deg(math.atan(y, x))))

-- Practical example: distance between two points
local function distance(x1, y1, x2, y2)
    return math.sqrt((x2 - x1)^2 + (y2 - y1)^2)
end

-- Angle between two points
local function angle(x1, y1, x2, y2)
    return math.deg(math.atan(y2 - y1, x2 - x1))
end

local point1 = {x = 0, y = 0}
local point2 = {x = 3, y = 4}

print(string.format("\nDistance between (%.0f,%.0f) and (%.0f,%.0f): %.2f", 
      point1.x, point1.y, point2.x, point2.y, 
      distance(point1.x, point1.y, point2.x, point2.y)))
print(string.format("Angle: %.2f°", 
      angle(point1.x, point1.y, point2.x, point2.y)))

Expected Output:

Trigonometric functions:
45° = 0.7854 radians
sin(45°) = 0.7071
cos(45°) = 0.7071
tan(45°) = 1.0000

Inverse trig functions for 0.5:
arcsin(0.5) = 30.00° 
arccos(0.5) = 60.00°
arctan(0.5) = 26.57°
atan2(1, 1) = 45.00°

Distance between (0,0) and (3,4): 5.00
Angle: 53.13°

Random Numbers

Generate random numbers for games, simulations, and testing:

-- Set random seed for reproducible results
math.randomseed(42)

print("Random number generation:")
print("Random float [0,1):", math.random())
print("Random integer [1,10]:", math.random(10))
print("Random integer [5,15]:", math.random(5, 15))

-- Generate multiple random numbers
print("\n10 random numbers [1,6] (dice rolls):")
for i = 1, 10 do
    io.write(math.random(6) .. " ")
end
print()

-- Random number utilities
local random_utils = {}

-- Random float in range [min, max)
function random_utils.random_float(min, max)
    return min + (max - min) * math.random()
end

-- Random element from table
function random_utils.random_choice(table)
    return table[math.random(#table)]
end

-- Shuffle array (Fisher-Yates algorithm)
function random_utils.shuffle(array)
    local n = #array
    for i = n, 2, -1 do
        local j = math.random(i)
        array[i], array[j] = array[j], array[i]
    end
    return array
end

-- Generate random boolean with probability
function random_utils.random_bool(probability)
    probability = probability or 0.5
    return math.random() < probability
end

-- Test the utilities
print("\nRandom utilities:")
print("Random float [2.5, 7.5):", random_utils.random_float(2.5, 7.5))

local colors = {"red", "green", "blue", "yellow", "purple"}
print("Random color:", random_utils.random_choice(colors))

local deck = {1, 2, 3, 4, 5}
print("Original deck:", table.concat(deck, ", "))
random_utils.shuffle(deck)
print("Shuffled deck:", table.concat(deck, ", "))

print("Random bool (70% true):", random_utils.random_bool(0.7))
print("Random bool (30% true):", random_utils.random_bool(0.3))

Expected Output:

Random number generation:
Random float [0,1):	0.639586188235
Random integer [1,10]:	9
Random integer [5,15]:	12

10 random numbers [1,6] (dice rolls):
4 1 6 2 5 3 6 1 4 2 

Random utilities:
Random float [2.5, 7.5):	5.8479309411764
Random color:	blue
Original deck:	1, 2, 3, 4, 5
Shuffled deck:	2, 5, 1, 4, 3
Random bool (70% true):	true
Random bool (30% true):	false

Number Formatting and Display

Format numbers for display and output:

-- Number formatting with string.format
local pi = math.pi
local large_number = 1234567.89
local small_number = 0.000123

print("Number formatting:")
print("Pi (default):", pi)
print("Pi (2 decimals):", string.format("%.2f", pi))
print("Pi (6 decimals):", string.format("%.6f", pi))
print("Pi (scientific):", string.format("%.2e", pi))

print("Large number:", large_number)
print("With commas:", string.format("%,.2f", large_number))  -- Note: Lua doesn't support comma separator directly

print("Small number:", small_number)
print("Scientific notation:", string.format("%.3e", small_number))

-- Custom number formatting function
local function format_number(num, decimals)
    decimals = decimals or 2
    local formatted = string.format("%." .. decimals .. "f", num)
    
    -- Add comma separators (simple implementation)
    local int_part, dec_part = formatted:match("([^.]+)(.?.*)")
    int_part = int_part:reverse():gsub("(%d%d%d)", "%1,"):reverse()
    if int_part:sub(1, 1) == "," then
        int_part = int_part:sub(2)
    end
    
    return int_part .. dec_part
end

print("\nCustom formatting:")
print("1234567.89:", format_number(1234567.89))
print("1000000:", format_number(1000000, 0))
print("123.456:", format_number(123.456, 3))

-- Percentage formatting
local function format_percentage(value, decimals)
    decimals = decimals or 1
    return string.format("%." .. decimals .. "f%%", value * 100)
end

local ratios = {0.75, 0.1234, 0.05}
print("\nPercentage formatting:")
for _, ratio in ipairs(ratios) do
    print(ratio .. " as percentage:", format_percentage(ratio))
end

-- Currency formatting
local function format_currency(amount, symbol)
    symbol = symbol or "$"
    return symbol .. format_number(amount, 2)
end

local prices = {99.99, 1500, 0.50}
print("\nCurrency formatting:")
for _, price in ipairs(prices) do
    print(price .. " as currency:", format_currency(price))
end

Expected Output:

Number formatting:
Pi (default):	3.1415926535898
Pi (2 decimals):	3.14
Pi (6 decimals):	3.141593
Pi (scientific):	3.14e+00
Large number:	1234567.89
With commas:	1234567.89
Small number:	0.000123
Scientific notation:	1.230e-04

Custom formatting:
1234567.89:	1,234,567.89
1000000:	1,000,000
123.456:	123.456

Percentage formatting:
0.75 as percentage:	75.0%
0.1234 as percentage:	12.3%
0.05 as percentage:	5.0%

Currency formatting:
99.99 as currency:	$99.99
1500 as currency:	$1,500.00
0.5 as currency:	$0.50

Practical Number Applications

Statistical Functions

-- Statistical calculation functions
local stats = {}

function stats.mean(numbers)
    if #numbers == 0 then return 0 end
    local sum = 0
    for _, num in ipairs(numbers) do
        sum = sum + num
    end
    return sum / #numbers
end

function stats.median(numbers)
    if #numbers == 0 then return 0 end
    local sorted = {}
    for _, num in ipairs(numbers) do
        table.insert(sorted, num)
    end
    table.sort(sorted)
    
    local n = #sorted
    if n % 2 == 1 then
        return sorted[(n + 1) / 2]
    else
        return (sorted[n / 2] + sorted[n / 2 + 1]) / 2
    end
end

function stats.standard_deviation(numbers)
    if #numbers <= 1 then return 0 end
    local mean = stats.mean(numbers)
    local sum_sq_diff = 0
    
    for _, num in ipairs(numbers) do
        sum_sq_diff = sum_sq_diff + (num - mean)^2
    end
    
    return math.sqrt(sum_sq_diff / (#numbers - 1))
end

function stats.min_max(numbers)
    if #numbers == 0 then return nil, nil end
    local min, max = numbers[1], numbers[1]
    for _, num in ipairs(numbers) do
        if num < min then min = num end
        if num > max then max = num end
    end
    return min, max
end

-- Test with sample data
local test_scores = {85, 92, 78, 96, 88, 91, 79, 87, 93, 82}

print("Statistical analysis of test scores:")
print("Scores:", table.concat(test_scores, ", "))
print("Mean:", string.format("%.2f", stats.mean(test_scores)))
print("Median:", stats.median(test_scores))
print("Standard deviation:", string.format("%.2f", stats.standard_deviation(test_scores)))

local min_score, max_score = stats.min_max(test_scores)
print("Range:", min_score .. " - " .. max_score)
print("Spread:", max_score - min_score)

Financial Calculations

-- Financial calculation functions
local finance = {}

-- Simple interest: A = P(1 + rt)
function finance.simple_interest(principal, rate, time)
    return principal * (1 + rate * time)
end

-- Compound interest: A = P(1 + r/n)^(nt)
function finance.compound_interest(principal, rate, time, compounds_per_year)
    compounds_per_year = compounds_per_year or 1
    return principal * (1 + rate / compounds_per_year) ^ (compounds_per_year * time)
end

-- Monthly payment for loan: PMT = P * [r(1+r)^n] / [(1+r)^n - 1]
function finance.loan_payment(principal, annual_rate, years)
    local monthly_rate = annual_rate / 12
    local num_payments = years * 12
    
    if monthly_rate == 0 then
        return principal / num_payments
    end
    
    return principal * (monthly_rate * (1 + monthly_rate)^num_payments) / 
           ((1 + monthly_rate)^num_payments - 1)
end

-- Break-even point
function finance.break_even(fixed_costs, price_per_unit, variable_cost_per_unit)
    return fixed_costs / (price_per_unit - variable_cost_per_unit)
end

-- Example calculations
print("\nFinancial calculations:")

-- Investment comparison
local principal = 10000
local rate = 0.06  -- 6%
local time = 5

local simple = finance.simple_interest(principal, rate, time)
local compound = finance.compound_interest(principal, rate, time, 12)  -- Monthly compounding

print(string.format("Investment of $%.2f at 6%% for 5 years:", principal))
print(string.format("Simple interest: $%.2f", simple))
print(string.format("Compound interest (monthly): $%.2f", compound))
print(string.format("Difference: $%.2f", compound - simple))

-- Loan payment calculation
local loan_amount = 250000
local loan_rate = 0.045  -- 4.5%
local loan_years = 30

local monthly_payment = finance.loan_payment(loan_amount, loan_rate, loan_years)
local total_paid = monthly_payment * loan_years * 12
local total_interest = total_paid - loan_amount

print(string.format("\nMortgage calculation ($%.0f at %.1f%% for %d years):", 
      loan_amount, loan_rate * 100, loan_years))
print(string.format("Monthly payment: $%.2f", monthly_payment))
print(string.format("Total paid: $%.2f", total_paid))
print(string.format("Total interest: $%.2f", total_interest))

Expected Output:

Statistical analysis of test scores:
Scores:	85, 92, 78, 96, 88, 91, 79, 87, 93, 82
Mean:	87.10
Median:	87.5
Standard deviation:	5.95
Range:	78 - 96
Spread:	18

Financial calculations:
Investment of $10000.00 at 6% for 5 years:
Simple interest: $13000.00
Compound interest (monthly): $13488.50
Difference: $488.50

Mortgage calculation ($250000 at 4.5% for 30 years):
Monthly payment: $1266.71
Total paid: $456015.35
Total interest: $206015.35

Common Pitfalls

  • Floating-point precision: Be aware of floating-point arithmetic limitations
  • Division by zero: Always check for zero divisors
  • Integer overflow: Be mindful of integer limits in calculations
  • Rounding errors: Use appropriate rounding for financial calculations
  • Angle units: Math functions use radians, not degrees

Checks for Understanding

  1. What's the difference between / and // operators?
  2. How do you convert degrees to radians?
  3. What does math.random(5, 10) return?
  4. How do you format a number to 3 decimal places?
  5. What's the result of math.floor(-3.7)?
Show answers
  1. / performs regular division (returns float), // performs floor division (returns integer)
  2. Use math.rad(degrees) or multiply by math.pi / 180
  3. A random integer between 5 and 10 (inclusive)
  4. Use string.format("%.3f", number)
  5. -4 (floor rounds down toward negative infinity)

Next Steps

Now that you understand number operations and the math library, you're ready to explore control flow statements and learn how to use conditions to make decisions in your programs.