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 three lines — a product name, a quantity, a price — and prints one line shaped <name>: <qty> @ $<price> = $<total>, where the total is quantity times price and both money amounts carry exactly two decimals.

The starter reads all three values and leaves the format string as a comment for you to finish.

Two mistakes the grader will catch. Handing the price to %d aborts the whole run with number has no integer representation, because tonumber("0.50") is a float. And formatting the total with %s, or building the line with .., prints 1.5 where the test demands 1.50 — those forms show you the number Lua stores, not the number a receipt needs.

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…