Grade by Grade
A. Programming Concepts and Skills · Data Types, Files and Expressions

Type 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.

str"72"one-char str"H"int72float72.0int("72")float("72")chrordfloat()int()str() turns anyvalue into text
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.

-4-3-2-10-2.7floor: -3round: -3int / trunc: -2ceil: -2-2.7 lies between -3 and -2. Which neighbour you get depends on the function.
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 example

Example 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.

  1. (a) Mixing int and float widens the int automatically: 5.0, a float. This is an implicit conversion; no information is lost.
  2. (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.)
  3. (c) / always gives a float: 1.0.
  4. (d) A string times an int repeats: "33". No conversion happens.
  5. (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 example

Example 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).

  1. int("42") → Integer.parseInt("42"). Like Python, it throws an exception (NumberFormatException) on bad text.
  2. str(42) → String.valueOf(42) or "" + 42.
  3. 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.
  4. ord("A") → (int) 'A', which is 65. Java has a separate char type in single quotes; Python just uses one-character strings.
  5. 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 fun

Why 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 activity

Receipt parser

You need: a paper store receipt (or a typed-up copy of one) and Python.

  1. 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".
  2. 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.
  3. Total the prices. Then repeat the total in whole cents: round(float(piece) * 100). Print both totals.
  4. 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

1

Give the result of int(-6.8), math.floor(-6.8), math.ceil(-6.8) and round(-6.8).

2

Which of these raise ValueError? int(" 8 "), int("8.5"), float("8.5"), int(""), float("1e3")

3

What are ord("C"), chr(100) and ord("7") - ord("0")?

4

What is int("777", 8)? And int("777")?

5

Predict the type and value: 5 // 2.0.

6

Write one expression that converts the text "3.9" to the int 4 (nearest).

7

What does sum([True, False, True, True]) give, and why?

More practice: warm-up

1

What is str(25) + "5"?

2

What is int("25") + 5?

3

What is float(4)?

4

What is chr(65)?

5

Is int to float a widening or narrowing conversion?

More practice: on your own

1

A 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.

2

Write an expression that turns a lower-case letter ch into its upper-case version using ord and chr only.

3

A classmate says int("12 ") (with a space after the 2) raises a ValueError. Are they right? Test the claim before you answer.

4

Use parse_int from this lesson. What happens with parse_int("3a") and why?

5

Why does Java require a cast for double to int but not for int to double?

Challenge

1

Extend parse_int to accept a leading + and to reject input such as "--5". Which lines change?

2

int("٣") 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?

3

Write 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.

Check your understanding

Quiz

1.

What is int(-4.9)?

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 teachers

Big 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.