Skip to content

Step 1 of 5 · Reading · ~3 min

Learn

Strings

String Formatting

string.format is Lua's answer to "print this number with exactly two decimals". You give it a template full of % placeholders plus the values to drop in, and it returns a string. The specifiers come straight from C's printf, so they will look familiar if you have met them anywhere else.

print(string.format("%d items", 3))
print(string.format("%.2f", 1.5))
print(string.format("%s scored %d", "Ada", 95))

--> 3 items
--> 1.50
--> Ada scored 95

The middle line is the whole reason this function exists. Plain print(1.5) gives 1.5; %.2f gives 1.50. Money, percentages and report columns all need the trailing zero that ordinary printing throws away.

The specifiers worth knowing

SpecifierTakesExampleResult
%dan integer("%d", 42)42
%fa number("%f", 1.5)1.500000
%.2fa number("%.2f", 1.5)1.50
%sanything("%s", true)true
%xan integer("%x", 255)ff
%qa string("%q", 'a"b')"a\"b"
%%nothing("100%%")100%

%s is the forgiving one: it calls tostring on whatever arrives, so a boolean, a table or a nil all format without complaint. That makes it ideal for debugging and wrong for money.

Width and alignment

A number between the % and the letter sets a minimum width. A minus sign left-aligns instead of right-aligning.

print(string.format("[%5d]", 42))
print(string.format("[%-5d]", 42))
print(string.format("[%8.2f]", 3.14159))
print(string.format("[%-8s]", "ok"))

--> [   42]
--> [42   ]
--> [    3.14]
--> [ok      ]

This is how you line up columns without counting spaces by hand.

The trap: %d refuses real floats

%d demands a value that has an exact integer representation. A float like 3.0 qualifies. A float like 2.5 does not — and it is a hard error, not a silent rounding.

✗ float into %d

print(string.format("%d", 2.5))

--> bad argument #2 to 'string.format' (number has no integer representation)

✓ convert first, or use a float specifier

print(string.format("%d", math.floor(2.5)))
print(string.format("%.2f", 2.5))

--> 2
--> 2.50

This bites because tonumber decides the subtype from the text: tonumber("3") is an integer and formats happily with %d, while tonumber("3.0") is a float that happens to survive and tonumber("3.5") is a float that does not.

Literal symbols in the template

Everything in the template that is not a % placeholder is copied through untouched, so currency symbols and punctuation need no escaping. Only the percent sign itself does, written twice.

print(string.format("total: $%.2f (%d%% off)", 12.5, 20))

--> total: $12.50 (20% off)

Your exercise

Format Receipt reads a list of purchases and prints them as an aligned table with a grand total underneath. Every column has a fixed width, so the amounts line up whatever the numbers are.

The starter reads and multiplies everything, and leaves the two print calls to you. Its for loop (one pass per purchase) and the match call that splits each line are given and need no changes: the Loops lesson explains for, and the String Patterns lesson right after this one explains match. The mistakes the grader will catch: handing a price or a line total to %d aborts the whole run with number has no integer representation, because tonumber("0.50") is a float; and formatting money with %s or .. prints 1.5 where the table needs 1.50, since those forms show you the number Lua stores, not the number a receipt needs. Watch the alignment too: a missing minus sign right-aligns the item names instead of left-aligning them.

Up nextString Patterns and MatchingStrings

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…