A. Programming Concepts and Skills · Data Types, Files and ExpressionsType Conversion: Strings, Characters and Numbers
A1.2 A4.1About 70 minutes
Learning goals- I can convert between strings, integers, floats and characters, and predict the result of each conversion.
- I can tell implicit (widening) conversions from explicit (narrowing) ones and explain which can lose information.
- I can choose among
int, round, math.floor, math.ceil and math.trunc when converting a float to an int. - I can convert user input safely with
try/except ValueError and use the error message to fix problems.
Words to know
type conversion
Producing a value of one type from a value of another, e.g. int("42").
implicit conversion (coercion)
A conversion the language does automatically, such as an int becoming a float in 3 + 2.0.
explicit conversion (cast)
A conversion the programmer asks for, such as int(3.9) in Python or (int) 3.9 in Java.
widening conversion
A conversion to a type that can hold every value of the original (int to float), so normally no information is lost.
narrowing conversion
A conversion that can lose information, such as float to int (the decimal part is lost).
code point
The number Unicode assigns to a character: ord("A") is 65, ord("é") is 233.
parsing
Reading text and turning it into a structured value such as a number.
Data never arrives in the type you want
A user types 17 into a form. A file contains the line Thunder Bay,-21.5. A web service sends back "2026". In each case the program receives text, but needs numbers. Going the other way, numbers must become text before they are shown, saved or sent.
In ICS3U you used int(), float() and str(). This lesson is about using them like a professional: knowing exactly what each conversion does at the edges, which conversions can lose information, how characters are really numbers, and how to stop bad input from crashing your program.
The main conversions. int and float read numbers from text; str() turns anything into text; ord and chr move between a character and its Unicode code (72 is "H"). int() on a float truncates toward zero.Float to int: four different answers
Converting a float to an integer is a narrowing conversion: the decimal part has to go somewhere. Python gives you a choice:
int(x) and math.trunc(x) truncate: drop the decimals, moving toward zero.math.floor(x) rounds down (toward negative infinity).math.ceil(x) rounds up (toward positive infinity).round(x) goes to the nearest integer; an exact half goes to the even neighbour.
For positive numbers, int and floor agree. For negative numbers they do not, and that difference is a classic bug.
Four ways to turn −2.7 into an integer. math.floor goes down (−3), math.ceil goes up (−2), int() and math.trunc go toward zero (−2), and round goes to the nearest (−3).import math
x = -2.7
print(int(x), math.trunc(x), math.floor(x), math.ceil(x), round(x))
print(round(2.5), round(3.5), round(2.675, 2))
Float-to-integer conversions. round sends exact halves to the even neighbour, and 2.675 is stored as slightly less than 2.675 (Lesson 6 explains why), so it rounds to 2.67. Output, line by line: -2 -2 -3 -2 -3 | 2 4 2.67
Strings to numbers and back
int(text) accepts optional spaces around the number, an optional sign, digits, and underscores between digits. It rejects a decimal point (int("4.0") is a ValueError). Give a second argument to read other bases: int("ff", 16) is 255.
float(text) accepts decimals, scientific notation ("6.02e23"), and the special values "inf" and "nan".
str(value) works on any value. For control over the look (decimal places, padding, commas), use f-strings such as f"{price:.2f}" or f"{n:,}".
print(int(" 42 "), int("-17"), int("1_000"))
print(int("ff", 16), int("101", 2), int("0o17", 8))
print(float("6.02e23"), float("-0.5"), float("inf"))
print(str(3.0), str(1e16), str(True))
int() ignores surrounding spaces and accepts underscores and a base. float() reads scientific notation and even "inf". str() of a large float switches to scientific notation. Output, line by line: 42 -17 1000 | 255 5 15 | 6.02e+23 -0.5 inf | 3.0 1e+16 True
Characters are numbers
Every character has a code point in Unicode (you met ASCII and Unicode in ICS3U). ord(ch) gives the code; chr(n) gives the character back. Digits, capital letters and small letters each run in order:
"0" to "9" are codes 48 to 57,"A" to "Z" are 65 to 90,"a" to "z" are 97 to 122.
That means ord(ch) - ord("0") turns a digit character into its value, and ord(ch) - ord("A") turns a capital letter into its position 0 to 25. Here is how int() could be written by hand:
def parse_int(text):
"""Convert a string of decimal digits (optional leading '-') to an int,
without using int(). Raises ValueError for anything else."""
text = text.strip()
sign = 1
if text.startswith("-"):
sign, text = -1, text[1:]
if text == "":
raise ValueError("no digits")
value = 0
for ch in text:
if not "0" <= ch <= "9":
raise ValueError("not a digit: " + ch)
value = value * 10 + (ord(ch) - ord("0"))
return sign * value
print(parse_int("2026") + 1, parse_int(" -45 "))
What int() does inside: each character's code minus the code of "0" gives its digit value (ord("7") - ord("0") is 7), and value * 10 + digit shifts the earlier digits left. Output: 2027 -45
def shift_letter(ch, k):
"""Shift an upper-case letter k places along the alphabet, wrapping Z to A."""
if "A" <= ch <= "Z":
return chr((ord(ch) - ord("A") + k) % 26 + ord("A"))
return ch
print("".join(shift_letter(c, 3) for c in "HELLO, ZOE"))
print(ord("A"), ord("a"), chr(233), ord("é"))
Character-to-integer and back. Converting a letter to a number 0 to 25, adding, wrapping with % 26 (Lesson 1) and converting back is the Caesar cipher. Output, line by line: KHOOR, CRH | 65 97 é 233
Worked exampleExample 1: Implicit versus explicit conversion
Predict the type and value of each expression: (a) 3 + 2.0 (b) True + True (c) 7 / 7 (d) "3" * 2 (e) "3" + 2.
- (a) Mixing
int and float widens the int automatically: 5.0, a float. This is an implicit conversion; no information is lost. - (b) In Python,
bool is a subtype of int with True = 1 and False = 0, so the answer is the int 2. (Useful: sum(flags) counts how many are True.) - (c)
/ always gives a float: 1.0. - (d) A string times an int repeats:
"33". No conversion happens. - (e)
TypeError. Python will not implicitly convert between text and numbers, because it cannot know which you mean ("32" or 5). You must convert explicitly: int("3") + 2 or "3" + str(2).
(a) float 5.0 (b) int 2 (c) float 1.0 (d) str "33" (e) TypeError.
Conversions that can fail
Some conversions cannot work: int("seventeen"), float(""), int("4.0"). Python raises ValueError with a message that quotes the bad text, such as
ValueError: invalid literal for int() with base 10: '4.0'
Read it carefully: it tells you the function (int()), the base, and the exact text, including any hidden spaces or quotes. Never let a conversion of user input or file data run unprotected. Wrap it in try/except ValueError and decide what to do: ask again, skip the line, or use a default.
def read_age(text):
"""Return the age in text as an int, or None if it is not a whole number 0-130."""
try:
age = int(text)
except ValueError:
return None
if 0 <= age <= 130:
return age
return None
for t in ["17", " 17 ", "17.0", "seventeen", "-3"]:
print(repr(t), read_age(t))
Safe conversion: try/except ValueError catches text that is not a whole number, and a range check catches numbers that make no sense. repr shows the quotes and spaces. Output, line by line: '17' 17 | ' 17 ' 17 | '17.0' None | 'seventeen' None | '-3' None
Debugging with the documentation (A4.1)
If a conversion surprises you, check the official definition: run help(int) or help(round) in the Python shell, or look up Built-in Functions on docs.python.org. For example, the documentation of round explains the round-half-to-even rule and warns about floats such as 2.675.
Worked exampleExample 2: The same ideas in Java
In university you will probably use Java or C++ as well as Python. Match each Python conversion with its Java version: int("42"), str(42), int(3.9), ord("A"), chr(66).
int("42") → Integer.parseInt("42"). Like Python, it throws an exception (NumberFormatException) on bad text.str(42) → String.valueOf(42) or "" + 42.int(3.9) → the cast (int) 3.9, which also truncates to 3. Java requires the cast because a double to int conversion can lose information (a narrowing conversion). Going from int to double is widening and happens automatically, like Python's 3 + 2.0.ord("A") → (int) 'A', which is 65. Java has a separate char type in single quotes; Python just uses one-character strings.chr(66) → (char) 66, which is 'B'.
Python uses conversion functions; Java uses parse methods and casts. In both, narrowing conversions must be asked for explicitly.
Just for funWhy did the float apply for a job as an integer?
It wanted to get to the point. Unfortunately, int() just cut it off.
Hands-on activityReceipt parser
You need: a paper store receipt (or a typed-up copy of one) and Python.
- Type five lines from the receipt into a Python list of strings, exactly as printed, for example
"MILK 2L 5.49" or "APPLES 1.2 kg @ 4.39/kg 5.27". - Write a function that takes one line, splits it with
.split(), converts the last piece with float(), and returns it. Wrap the conversion in try/except ValueError and return None if it fails. - Total the prices. Then repeat the total in whole cents:
round(float(piece) * 100). Print both totals. - Add a deliberately bad line such as
"THANK YOU" and show that your program skips it instead of crashing.
What to look for: every conversion protected, the bad line skipped, and a cents total that matches the receipt exactly. If the float total shows something like 27.959999999999997, that is not a bug in your code; Lesson 6 explains it.
Your turn
1Give the result of int(-6.8), math.floor(-6.8), math.ceil(-6.8) and round(-6.8).
2Which of these raise ValueError? int(" 8 "), int("8.5"), float("8.5"), int(""), float("1e3")
int("8.5") and int(""). The others give 8, 8.5 and 1000.0.
3What are ord("C"), chr(100) and ord("7") - ord("0")?
4What is int("777", 8)? And int("777")?
511 (7 × 64 + 7 × 8 + 7) and 777.
5Predict the type and value: 5 // 2.0.
A float, 2.0. One float operand makes the result a float.
6Write one expression that converts the text "3.9" to the int 4 (nearest).
round(float("3.9")). int("3.9") would raise a ValueError.
7What does sum([True, False, True, True]) give, and why?
3. bool is a subtype of int, with True = 1, so the sum counts the True values.
More practice: warm-up
5Is int to float a widening or narrowing conversion?
More practice: on your own
1A program reads "12.50" from a file and needs the number of whole dollars. Which is safer, int(float(s)) or math.floor(float(s)), if the file can contain refunds such as "-12.50"? Explain.
It depends on the meaning. For −12.50, int gives −12 and floor gives −13. If "whole dollars" means the amount without the cents, use int (truncation). Be explicit in the comments about which you chose and why.
2Write an expression that turns a lower-case letter ch into its upper-case version using ord and chr only.
chr(ord(ch) - 32). Lower-case codes are exactly 32 more than capitals ("a" is 97, "A" is 65). In real code use ch.upper(), which also handles letters like é.
3A classmate says int("12 ") (with a space after the 2) raises a ValueError. Are they right? Test the claim before you answer.
No. int("12 ") is 12, because int() ignores spaces at both ends. Always reproduce an error yourself before trusting someone's description of it.
4Use parse_int from this lesson. What happens with parse_int("3a") and why?
It raises ValueError: not a digit: a. The loop checks every character, and "a" is not between "0" and "9".
5Why does Java require a cast for double to int but not for int to double?
double to int is narrowing: the decimal part (and possibly huge values) can be lost, so Java makes the programmer say so explicitly. int to double is widening and safe for every int value, so it happens automatically.
Challenge
1Extend parse_int to accept a leading + and to reject input such as "--5". Which lines change?
Add elif text.startswith("+"): text = text[1:] after the minus check. "--5" is already rejected: after removing one -, the text "-5" contains -, which is not a digit, so a ValueError is raised.
2int("٣") returns 3 in Python (٣ is the Arabic-Indic digit three). Why might this matter when validating input, and how could you accept only the ASCII digits 0 to 9?
Python's int() accepts any Unicode decimal digit, so a validator built on int() or str.isdigit() lets through characters a later system (a database, a Java program) may reject. To allow only 0 to 9, check each character with "0" <= ch <= "9" (as parse_int does) or use ch in "0123456789".
3Write to_int_halfup(x) that rounds a non-negative float to the nearest int with halves always going up (2.5 → 3), unlike round. Explain any remaining limitation.
return math.floor(x + 0.5). For 2.5 it gives 3 and for 3.5 it gives 4. Limitation: values that are not stored exactly may still round the "wrong" way: 1.005 * 100 is computed as 100.49999999999999, so to_int_halfup(1.005 * 100) gives 100, not 101; exact decimal rounding needs the decimal module (Lesson 6).
Check your understandingQuiz
2. Which call raises a ValueError?
3. What is chr(ord("a") + 2)?
4. Which is a narrowing conversion?
5. What is the type of True + 2.5?
6. What is math.floor(-0.5)?
7. Which Java expression matches Python's int("42")?
For parents and teachersBig idea: A1.2 asks for an understanding of type conversion: string-to-integer, character-to-integer, integer-to-character and floating point-to-integer. The try/except work and the help() tip support A4.1 (using documentation to solve problems independently).
How to help: ask your teen to explain out loud why int("7.0") fails but int(7.0) works (the first is text that is not written as a whole number; the second is a float that is truncated).
Common difficulties:
- Assuming
int() rounds. It truncates. - Mixing up
ord (character → number) and chr (number → character). - Using
int and floor interchangeably for negative numbers. - Converting input without
try/except, so one typo crashes the program.