C from the ground up

Whole numbers and decimals

The two kinds of number C cares about, why dividing two whole numbers throws the fraction away, and how to print each kind cleanly.

Part 2 of 12

Beginner14 min readBack to Courses

Two kinds of number

C is picky about numbers. A value stored in an int is a whole number with no fractional part. A value stored in a double can hold a fraction, which is what you want for prices and measurements.

You name a value by giving it a type and a name: int count = 3; or double price = 1.5;. The type fixes what the value can be, and the compiler will not let you quietly mix them.

Integer division truncates

This surprises everyone once. When both sides of a division are whole numbers, C does whole number division: 7 / 2 gives 3, not 3.5. The fraction is simply dropped, never rounded.

The companion is %, the remainder operator. 7 % 2 gives 1, the amount left over. Together, division and remainder tell you how many whole groups fit and how much is left.

Decimal numbers and printing

If either side is a double, the division is a decimal one: 7.0 / 2 gives 3.5. Note the decimal point on the seven; that single dot changes the answer.

When you print, match the format to the type. %d is for whole numbers and %f is for decimals. Written bare, %f always shows six decimal places, so %.1f or %.2f is how you ask for a tidy shorter one.

What the type actually stores

An int is usually 32 bits, which is room for about two billion values in each direction. Go past that and a signed int overflows; C does not stop you, and the answer becomes unpredictable. Check before you add, or choose a wider type.

A double stores a binary fraction, so 0.1 has no exact form. Printing 0.1 + 0.2 with many digits shows 0.30000000000000004. That is not a fault in C but how binary decimals work, and it is why you compare decimal values with a tolerance rather than with ==.

Sizes, and the limits header

sizeof answers how many bytes a type occupies on the machine in front of you. sizeof(int) is four on most desktop systems and two on some small embedded chips, and the language only promises a minimum range, not a fixed width. <limits.h> publishes the exact bounds as INT_MAX and INT_MIN, so a program can check rather than guess.

Use those names instead of writing 2147483647 by hand. The constant is right for one compiler and wrong for another, while INT_MAX is right wherever the file is built. The same header has CHAR_MAX and LONG_MAX, and <float.h> does the same job for the decimal types.

Money, and why cents beat decimals

A price of 4.20 has no exact binary form, so adding it a hundred times drifts away from 420. The usual fix is to store money as a whole number of cents and divide by 100 only when you print. Every addition and subtraction is then exact, and the only rounding happens once, at the edge.

The same habit applies to measurements that must add up: store the smallest unit, such as grams or millimetres, as an int. Keep the decimal type for values that are measured rather than counted — an average, a ratio, a temperature — where a tiny error is acceptable and a fraction is the point.

Mixing the two kinds

When an expression mixes an int and a double, the int is converted to double before the operation. That is why 7.0 / 2 keeps its fraction — and why (double)total / count is needed when total is an int. The cast has to land before the division, not after it.

Assigning a double to an int truncates toward zero, and the compiler may warn. int half = 3.5; stores 3, not 4. If the fraction matters, keep the value as a double all the way to the print.

Three mistakes account for most number bugs: printing a double with %d, expecting 7 / 2 to give 3.5, and testing two doubles with ==. Match the marker to the type, put a decimal point on one side of the division, and compare decimals with a small tolerance.

Try these next

  • Print 7 / 2, 7 % 2 and 7.0 / 2 on three lines, and write a comment explaining each answer.
  • Store a price in an int as cents, then print it in pounds with %.2f after dividing by 100.0.
  • Add one to the largest int you can write and see what %d reports.

One of each kind

#include <stdio.h>

int main(void) {
    int whole = 7;
    double price = 1.5;

    printf("Whole %d\n", whole);
    printf("Price %.2f\n", price);
    return 0;
}

Groups and leftovers

Run it, then change the two starting numbers and predict both the count each person gets and the remainder before you run again.

Output
Click "Run" to execute your code

Split a bill, keep the cents

Run it, then change the bill and the number of people. Predict the share and the leftover cents before you run it again.

Output
Click "Run" to execute your code

Change the seven to a seven point zero in the division and watch the middle line change shape. That one dot is the whole lesson of integer division.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does the expression 7 / 2 give when both values are int?