Learn C Programming

Lesson 7 of 8 · Operators and Type Conversion

Module 4 · Operators and Type Conversion

Problems: Expressions That Bite

FreeProblems

In this lesson

  • Pick the right operator for a task before you write the first line.
  • Predict the sign of a result when an input is allowed to be negative.
  • Test an expression at the two edges the statement already told you about.

Ten problems, graded against hidden tests. Eight of them you already met inside the six lessons. Two are new.

The last module was about choosing a type. This one is about choosing an operator, and then about what that operator does at the edges.

Almost every failure below is a sign or a width. The value is right for the sample and wrong for one input the statement warned you about.

What is new since the last problem set

Module 2's problems each had one idea. Here several of them have two, and the second one only appears at an edge.

Four problems accept a negative input. Three of those use / or %, where the sign rule of lesson 1 decides the answer.

Three problems work on bits, where the type has to be unsigned and the literal has to carry a u.

So read Constraints before the story, and note every place a minus sign is allowed.

Still no if and still no loop

Modules 5 and 6 have not happened. Nothing below needs if, for or while, and nothing below is meant to have one.

Four problems ask a yes or no question. The answer is a comparison, which is an expression worth 1 or 0, printed with %d.

Three problems need a remainder from a value that may be negative, where lesson 1's sign rule decides the answer.

So if you reach for a construct you have not met, the problem is telling you to reread a lesson rather than to look ahead.

The two sign rules that decide four of these

Both are from lesson 1, and both are worth saying again before you start.

Integer division cuts toward zero. -9 / 2 is -4, not -5. The fraction is dropped, and dropping is not rounding down.

The remainder takes the sign of the left operand. -7 % 3 is -1, not 2. So "is this divisible" compares the remainder against 0, never against a positive value.

A program that compares a remainder against 1 or against a positive number will pass every non-negative test and fail the first negative one.

unsigned, and where the u goes

Three problems here work on bits. All three read into an unsigned int with %u and print with %u.

Inside them, every mask is written 1u << k, never 1 << k. With a bit number of 31 the second one is undefined behaviour, and the Playground will not warn you.

One problem, the last, is the opposite lesson: it is about what goes wrong when an unsigned value meets a signed one in a comparison.

The forms these ten problems need

scanf("%d", &n)             an int, which may be negative
scanf("%u", &n)             an unsigned int, for the bit problems
printf("%d\n", a < b)       a comparison, printed as 1 or 0
printf("%u\n", v)           an unsigned value

n / 10 % 10                 a digit out of the middle of a number
n % d == 0                  divisible, and correct for negative n
v |=  (1u << k)             set bit k
v &= ~(1u << k)             clear bit k
v ^=  (1u << k)             toggle bit k
(v >> k) & 1u               test bit k, worth 1 or 0
  • Every starter below already has its scanf line written. Leave it as it is.
  • A yes or no answer is printed with %d and is 1 or 0. No words, no if.
  • Where a statement says unsigned, the u on the literal is part of the answer.
Example 1: a whole problem, solved, with the sign rule applied

Is one number a multiple of another, when the first one may be negative?

#include <stdio.h>

int main(void)
{
    int n = 0;
    int d = 0;
    scanf("%d %d", &n, &d);

    printf("%d\n", n % d == 0);
    return 0;
}
1

That output is for the input -91 7. The remainder of a negative multiple is still exactly 0, so comparing against 0 is the version that survives a minus sign.

Run in Compiler
Example 2: the three bit idioms in one program

Three problems below are this program with different bit numbers.

#include <stdio.h>

int main(void)
{
    unsigned int v = 0;
    scanf("%u", &v);

    v |= (1u << 2);
    printf("%u\n", v);

    v &= ~(1u << 0);
    printf("%u\n", v);

    v ^= (1u << 2);
    printf("%u\n", v);
    return 0;
}
5
4
0

That output is for the input 1. Setting bit 2 of 1 gives 5, clearing bit 0 gives 4, and toggling bit 2 again gives 0.

Run in Compiler
Example 3: an expression typed the way the statement wrote it

One problem below asks for exactly this: no brackets of your own, and the table's own answer.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    printf("%d\n", a * b % c);
    printf("%d\n", a + b > c);
    return 0;
}
-1
0

That output is for the input -5 3 7. The first line is (a * b) % c, and -15 leaves a remainder of -1. The second is (a + b) > c, which is -2 against 7.

Run in Compiler

Where this is used

  • A judge is a contest. Every one of these ten has the shape a contest problem has: input, output, constraints, hidden tests. The Progsity judge runs them the same way it runs a contest round.
  • An interview whiteboard. "Is this a leap year" and "swap the nibbles of a byte" are both real first-round questions, and both are one expression each.
  • A code review. "What does this line do at the edge of its range" is the question a reviewer asks. The hidden tests below ask it for you.
  • A regression suite. Every test file here is a pair: an input and the output somebody worked out by hand. That is what a test is, in every language and every company.

Common mistakes

1. Comparing a remainder against a positive number.

printf("%d\n", n % 2 == 1);

No message at either command line. For n of -7 the remainder is -1, so this answers 0 for an odd number. Compare against 0 and invert, or use != 0.

2. Writing the mask without the u.

v |= (1 << k);

Silent at every command line. For k of 31 this is undefined behaviour, so no output is quoted for it. Seven of the eight hidden tests would still pass, which is what makes this one expensive.

3. Unpacking from the inputs instead of the packed value.

printf("%u %u %u\n", r, g, b);

No message, and every test passes. It also proves nothing: the program never read a single bit back. The statement asks you to unpack from the packed value for exactly that reason.

4. Adding brackets to a problem that asked for none.

printf("%d\n", a * (b % c));

No message, and the sample may still pass. a * b % c is (a * b) % c, and the two differ as soon as b is smaller than c. Type the expression the statement wrote.

Brain teaser

Bob's flag-console passes seven of its eight hidden tests. He changed one character to make it pass the eighth.

v |= (1 << i);
v &= ~(1 << j);
v ^= (1 << k);

Name the character, and say which value in the constraints made the difference. Then answer the harder half. On the other seven tests the program really is correct, so seven of eight is an honest score. Say why a reviewer would still ask for that character.

Look at the type of the literal, not at the variable. Then ask what the largest bit number in the constraints is, and what a 1 lands on when it gets there.

Problem 1: digit-splitEasy

Maria runs a locker room where every code is exactly three digits. Her label printer wants the digits one at a time.

Input. One line with one integer n.

Output. One line with the three digits of n, separated by single spaces.

Constraints. 100 <= n <= 999.

Sample. Input 407 gives 4 0 7.

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    /* Hundreds with /, units with %, and the middle one needs both. */

    return 0;
}
Run in Compiler

Hint 1

Two operators do all three digits. / drops digits from the right, % keeps the last one.

Hint 2

The hundreds digit is one division. The units digit is one remainder. The tens digit needs a division first and then a remainder.

Solution

Divide by 100 for the hundreds, because integer division throws away everything to the right of it. Take the remainder on 10 for the units, because that is the part the last division would have dropped. For the tens, divide by 10 first, which moves the tens digit into the units place, and then take the remainder on 10.

All three read the original n, so the order of the three lines does not matter. Test with 100 and with 500. Both have a zero in the middle, and a program that reached for a subtraction often gets those wrong.

Problem 2: is-divisibleEasy

Zara is testing a scheduler that fires a job every d ticks, and wants a yes or no answer for a given tick.

Input. One line with two integers n and d.

Output. One line with 1 if d divides n exactly, otherwise 0.

Constraints. -1000000000 <= n <= 1000000000, and 1 <= d <= 1000000000.

Sample. Input 91 7 gives 1. Input -91 6 gives 0.

#include <stdio.h>

int main(void)
{
    int n = 0;
    int d = 0;
    scanf("%d %d", &n, &d);

    /* A comparison is worth 1 or 0, so print it with %d. No if needed. */

    return 0;
}
Run in Compiler

Hint 1

"Divides exactly" is another way of saying "the remainder is nothing left over".

Hint 2

n may be negative, and lesson 1 said what that does to the remainder. Pick the one value you can compare against that is the same in both signs.

Solution

The whole program is one comparison: the remainder of n on d, compared against 0. That comparison is an expression worth 1 or 0, so it goes straight into a printf with %d and needs no if at all.

Zero is the value that makes this work for negative input. A negative multiple leaves a remainder of exactly 0, with no sign to worry about. Every other comparison you might reach for behaves differently on the two sides of zero.

Problem 3: running-totalEasy

Maria's till closes with five sales on the slip, and she wants one running total built one sale at a time.

Input. One line with five integers. A sale may be negative, because a refund goes on the same slip.

Output. One line with their total.

Constraints. Each value is between -1000000 and 1000000.

Sample. Input 10 20 30 40 50 gives 150.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    int d = 0;
    int e = 0;
    scanf("%d %d %d %d %d", &a, &b, &c, &d, &e);

    int total = 0;
    /* Five += lines, one per value. */

    return 0;
}
Run in Compiler

Hint 1

One box holds the answer the whole way through. Each of the five values is added to it in place.

Hint 2

Lesson 3's += is the operator. The box has to start at 0, which the starter already does for you.

Solution

Declare the total at 0, then write five compound assignments, one per input. Each line names the total once and adds one value to it. The long form leaves room for a "wrote the wrong variable name" bug; this one does not.

The type is comfortable here on purpose. Five values of a million each is five million, far inside an int, so this problem is about the operator and not about the width. The negative tests are there because a refund is an ordinary sale with a minus sign.

Problem 4: leap-yearMedium

David is writing the calendar check every project eventually needs. A year is a leap year when it divides by 4 and not by 100, or when it divides by 400.

Input. One line with one integer y.

Output. One line with 1 if y is a leap year, otherwise 0.

Constraints. 1 <= y <= 999999.

Sample. Input 2024 gives 1. Input 1900 gives 0. Input 2000 gives 1.

#include <stdio.h>

int main(void)
{
    int y = 0;
    scanf("%d", &y);

    /* Three remainders, two comparisons joined by &&, then one ||. */

    return 0;
}
Run in Compiler

Hint 1

The rule has three separate questions in it, and each one is a remainder compared against 0.

Hint 2

Read the rule aloud and copy its shape. "This and not that" is one &&; "or the other" is one ||. Lesson 5 says && binds tighter, so no brackets are needed.

Solution

Write the three remainder tests, join the first two with &&, then join that group to the third with ||. The whole rule is one expression whose value is already 1 or 0, so it stores into an int and prints with %d.

1900 and 2000 are the two years that tell you whether you got it right, and both are in the hidden tests. A program that stops at "divides by 4" answers 1 for 1900, and a program that forgets the last clause answers 0 for 2000. Every other year in the tests agrees with all three versions.

Problem 5: five-valuesMedium

Kenji wants to prove he can read a line the way C reads it. His teacher gives him five expressions and three numbers.

Input. One line with three integers a, b and c.

Output. Five lines, the values of a + b * c, a - b - c, a * b % c, a < b && b < c and a + b > c, in that order.

Constraints. -1000 <= a, b <= 1000, and 1 <= c <= 1000.

Sample. Input 2 3 4 gives the five lines 14, -5, 2, 1, 1.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Type the five expressions exactly as written. Add no brackets. */

    return 0;
}
Run in Compiler

Hint 1

There is nothing to work out. Type the five expressions as the statement writes them and print each with %d.

Hint 2

The trap is helpfulness. Adding a bracket you think improves a line changes what it means, and the hidden tests measure the original.

Solution

Five printf lines, each holding one of the five expressions exactly as written, all with %d, because a comparison is an int too. The answers come out of lesson 5's table. * binds before +. - groups left to right. * and % share a row and group left to right. Comparison binds before &&, and arithmetic before comparison.

The third line is where the hidden tests bite. It means the remainder of the product. When a * b is negative the answer is negative too. A program that took the remainder first would give a different number.

Problem 6: expr-evalMedium

David is writing the smallest calculator anyone has ever shipped. It reads five numbers and evaluates a * b + c / d - e % d, exactly as C would.

Input. One line with five integers a, b, c, d and e.

Output. One line with the value of that expression.

Constraints. -1000 <= a, b, c, e <= 1000, and 1 <= d <= 1000.

Sample. Input 2 3 10 4 7 gives 5.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    int d = 0;
    int e = 0;
    scanf("%d %d %d %d %d", &a, &b, &c, &d, &e);

    /* One expression, typed exactly as the statement writes it. */

    return 0;
}
Run in Compiler

Hint 1

Three of the five operators are in row 3 of lesson 5's table. They all group before the + and the - get a turn.

Hint 2

Both c / d and e % d can be negative, and each takes its sign from its own left operand. Work the sample out on paper with a negative e before you type anything.

Solution

One printf holding the expression as written. C groups it as the product, plus the quotient, minus the remainder, because *, / and % share a precedence row that sits above + and -. No brackets change anything here, which is why none are needed.

The two signed pieces are the whole problem. A negative c makes the division cut toward zero rather than down, and a negative e makes the remainder negative, so subtracting it adds. The largest values are well inside an int: a thousand times a thousand is a million.

Problem 7: bit-packMedium

Maria's image tool stores a colour as three bytes packed into one number, then has to take them apart again.

Input. One line with three integers r, g and b.

Output. Two lines. The first is the packed value: red in bits 16 to 23, green in bits 8 to 15, blue in bits 0 to 7. The second is r, g and b read back out of it, separated by single spaces.

Constraints. 0 <= r, g, b <= 255.

Sample. Input 200 130 40 gives 13140520, then 200 130 40.

#include <stdio.h>

int main(void)
{
    unsigned int r = 0;
    unsigned int g = 0;
    unsigned int b = 0;
    scanf("%u %u %u", &r, &g, &b);

    /* Pack with two shifts and two ors. Unpack with shifts and 255u. */

    return 0;
}
Run in Compiler

Hint 1

Each value is at most 255, so each one occupies eight bits and no two of them overlap once they are moved apart.

Hint 2

Packing is two shifts joined by |. Unpacking is the same shifts in the other direction, each followed by a mask with 255u.

Solution

Move red 16 places up and green 8 places up, leave blue where it is, and join all three with |. Because each value fits in eight bits, the three slices sit side by side in the 32 bit number and nothing is lost. To read one back, shift it down to the bottom and mask it with 255u, which drops everything above the eight bits you want.

Unpack from the packed value, not from the three inputs. Printing the inputs again passes every test and proves nothing, because the program would never have read a single bit back. The interesting tests are the three where exactly one channel is nonzero: they catch a shift written in the wrong direction.

Problem 8: nibble-swapMedium

Amara is reading an old instrument format where every byte arrived with its two four bit halves the wrong way round.

Input. One line with one integer b.

Output. One line with b after its top four bits and its bottom four bits have exchanged places.

Constraints. 0 <= b <= 255.

Sample. Input 195 gives 60. Input 16 gives 1.

#include <stdio.h>

int main(void)
{
    unsigned int b = 0;
    scanf("%u", &b);

    /* Two halves: one moves up four places, the other moves down four. */

    return 0;
}
Run in Compiler

Hint 1

Four bits is the mask 15u, which is 1111. Both halves need it, for different reasons.

Hint 2

Take the low half, mask it, then move it up four. Take the high half by moving it down four, then mask it. Join the two with |.

Solution

Mask the bottom four bits with 15u and shift the result up four places. Shift the whole value down four places and mask that with 15u too. The two results occupy different halves of a byte, so | joins them into the answer.

The mask on the low half is what stops the answer from carrying bits that were never part of it. On this input range the value is only eight bits wide, so a program without that mask happens to be right. On any wider input it would not be. Writing the mask is the habit that survives the next problem.

Problem 9: flag-consoleMedium

Kenji's control board holds 32 switches in one number. A maintenance command names one switch to turn on, one to turn off and one to flip.

Input. One line with four integers: the starting value v, then the bit numbers i, j and k.

Output. Three lines: the value after setting bit i, then after clearing bit j, then after toggling bit k. Each step starts from the value the previous one left.

Constraints. 0 <= v <= 4294967295, and 0 <= i, j, k <= 31.

Sample. Input 0 3 3 5 gives the three lines 8, 0, 32.

#include <stdio.h>

int main(void)
{
    unsigned int v = 0;
    unsigned int i = 0;
    unsigned int j = 0;
    unsigned int k = 0;
    scanf("%u %u %u %u", &v, &i, &j, &k);

    /* Three idioms, in order: set, clear, toggle. Print after each one. */

    return 0;
}
Run in Compiler

Hint 1

The three idioms are in lesson 4, in order. Each one is a compound assignment on the same variable, followed by a printf.

Hint 2

The constraints allow a bit number of 31. That one value decides which letter your mask literal needs.

Solution

Three lines, in the order the statement gives: | with the mask to set, & with the flipped mask to clear, ^ with the mask to toggle. Each is a compound assignment, so the value carries forward, and a printf after each one reports the state the way the board does.

Every mask is 1u shifted, never 1. With a bit number of 31 a signed 1 would be shifted into the sign bit. That is undefined behaviour, and the Playground does not warn about it. The clear idiom is also the only one that needs ~, because the mask it wants is a zero in one place and ones everywhere else.

Problem 10: unsigned-fixHard

Maria's stock import left some counts negative. A limit check that stores its limit as an unsigned int has answered no for every one of them.

Input. One line with two integers: a signed count and a limit, which your program must read into an unsigned int.

Output. Two lines: the value of count < limit with the limit left unsigned, then the value of the same comparison done in signed arithmetic.

Constraints. -1000000 <= count <= 1000000, and 0 <= limit <= 1000000.

Sample. Input -1 1 gives 0, then 1.

#include <stdio.h>

int main(void)
{
    int count = 0;
    unsigned int limit = 0;
    scanf("%d %u", &count, &limit);

    /* First line: no cast. Second line: one cast on the limit. */

    return 0;
}
Run in Compiler

Hint 1

The first line is the bug, written on purpose. Do not fix it; the statement asks for both answers.

Hint 2

The limit is at most a million, so it fits an int comfortably. One cast on the right operand moves the whole comparison down a rung of lesson 6's ladder.

Solution

The first line compares the two as they are. The ladder raises count to unsigned int, where a negative value becomes a very large positive one, so the answer is 0 for every negative count. Writing that conversion out as a cast changes nothing about the result and stops a local gcc -Wall from warning about a conversion the task asked for.

The second line casts the limit to int. Both sides are then signed, the ladder has nothing to raise, and the comparison means what it reads as. That cast is safe here only because the constraints promise the limit fits an int. A program that casts the other way, from a value that might be negative, makes the bug worse.

Common doubts

  • Can I use if anyway, if I already know it?

    You can, and the judge will accept it. You will also have skipped the point: every one of these is one expression, and seeing that is what makes Module 5 easy.

  • Why do the bit problems insist on %u?

    Because a bit number of 31 is in the constraints. Read a value into a signed int and the shifts around it stop being defined.

  • My program is right for the sample and fails one hidden test. Where do I look?

    At the sign. Four of these ten allow a negative input, and in three of them the sign changes the answer of a / or a %.

  • Do I need brackets in five-values and expr-eval?

    No, and adding them changes the answer. Both problems are measuring what the table says, not what reads nicely.

  • Is the hint ladder counted against me?

    No. Opening a hint is recorded and costs nothing. Type the program yourself afterwards, because reading is not the skill.

Key takeaways

  • Read Constraints first and note every place a negative value is allowed.
  • A remainder is compared against 0, because its sign follows the left operand.
  • A yes or no answer is a comparison, printed with %d as 1 or 0.
  • Bit work uses unsigned values, %u, and a u on every mask literal.
  • An expression the statement wrote is typed as written; a bracket you add is a change.
  • A cast belongs on an operand, and only where the constraints prove it is safe.

Next comes the module test, and after it Module 5, where a comparison finally gets to decide which lines run.

Module test

Ten questions on this module. Pass at 70%, and you can take it as many times as you like.

Take the module test

End of lesson 7

Get every problem accepted, and the lesson is done.

0 of 10 problems accepted

Next: Module Test: Operators