Embercode
✕
C++ OperatorsLesson 5 of 14

55 min read

Module assignment: order desk calculator

The brief

You are the first programmer at a small online shop. Every order that comes into the system needs a short summary for the packing desk. The packing desk reads the summary on a screen, and they need five things for each order: the money owed, whether shipping is free, whether the delivery time is valid, and a compact code that holds the time so it can be sent to the warehouse printer. Your job is to write one C++ program that does all of that from a single line of input.

This assignment uses everything in the module. It needs integer division and remainder, compound updates, comparisons, logical operators, bit operators, and brackets where precedence could surprise a reader. It does not need loops or functions, because those come in later modules. Keep the program in one main function, and keep it readable. The packing desk will also ask you questions about your code, so each step must be something you can explain in plain words.

The work is split into five parts. Do them in order, because each part reuses a value from the one before. Test each part with the sample inputs before you move on.

What the program reads and prints

The program reads six whole numbers, separated by spaces, on one line. Type them in the Input box in this order:

  1. priceCents, the price of one item in cents. A price of 19.99 is written 1999.
  2. quantity, the number of items. It is at least 1.
  3. taxPercent, the tax rate as a whole percent. A rate of 7 percent is written 7.
  4. isMember, which is 1 for a member and 0 for anyone else.
  5. hours, the delivery hour, from 0 to 23 when valid.
  6. minutes, the delivery minute, from 0 to 59 when valid. For the packing code, it must stay from 0 to 63.

The program prints these lines, in this order, and each line uses the exact words below:

subtotal: <number>
tax: <number>
total: <number>
dollars: <number>, cents: <number>
free shipping: <true or false>
valid time: <true or false>
packed: <number>
unpacked hours: <number>, minutes: <number>

Use boolalpha so that the two yes-or-no lines print true or false as words, not 1 or 0.

Here are three sample inputs with the output you must produce. You will test against all three.

Sample A, input 1999 3 7 0 23 59:

subtotal: 5997
tax: 420
total: 6417
dollars: 64, cents: 17
free shipping: true
valid time: true
packed: 1531
unpacked hours: 23, minutes: 59

Sample B, input 250 2 10 0 24 15:

subtotal: 500
tax: 50
total: 550
dollars: 5, cents: 50
free shipping: false
valid time: false
packed: 1551
unpacked hours: 24, minutes: 15

Sample C, input 1000 1 0 1 12 60:

subtotal: 1000
tax: 0
total: 1000
dollars: 10, cents: 0
free shipping: true
valid time: false
packed: 828
unpacked hours: 12, minutes: 60

Notice that sample C prints unpacked hours: 12, minutes: 60 even though 60 is not a valid minute. The packing code can hold up to 63 in the minute field, so 60 fits and unpacks correctly. The valid-time line is the one that reports the problem. Packing and checking are separate jobs, and the output shows both.

Part 1: money in whole cents

The shop works only in whole cents, because money in decimal form causes rounding surprises. Your first job is the subtotal, the tax and the total.

The subtotal is the price times the quantity. In the first sample, 1999 times 3 is 5997 cents. That is a plain multiplication, and no special rule applies.

The tax is harder. A tax rate of 7 percent on 5997 cents is 419.79 cents. The shop rounds to the nearest cent, which gives 420. You must not use decimal numbers for this. Use the rounding trick from the first lesson: add half the divisor before dividing. For a divisor of 100, add 50. So the tax is (subtotal * taxPercent + 50) / 100. In the first sample, 5997 * 7 is 41979, plus 50 is 42029, and integer division by 100 gives 420.

Some students write subtotal * taxPercent / 100 without the 50. That gives 419 for sample A, which is one cent too small. Part 4 of this assignment shows that case in code, so you can see the difference for yourself.

The total is the subtotal plus the tax. The dollars part is total / 100, and the cents part is total % 100. Print the cents as a plain number, so 17 cents prints as 17. Do not try to add a leading zero yet, because that needs conditionals, which come in the next module.

Here is Part 1 as a program on its own. Type 1999 3 7 in the Input box. It should print the first three lines of sample A.

C++ · editable

The output is:

subtotal: 5997
tax: 420
total: 6417

Part 2: free shipping and the valid time

Shipping is free when the total is at least 5000 cents, or when the customer is a member. The rule is total >= 5000 || isMember != 0. Read the brackets in your head: the comparison runs first, then the OR. The != 0 on isMember turns the number into a clear yes or no. You could write isMember alone, because a non-zero number counts as true, but the explicit test tells the reader what you mean.

The valid time needs two range checks, each joined with AND. The hours must satisfy hours >= 0 && hours <= 23. The minutes must satisfy minutes >= 0 && minutes <= 59. The time is valid only when both checks are true. Keep each check in its own bool variable with a clear name, as the last lesson did. Then combine them in the print line with validHours && validMinutes.

Here is Part 2 on its own. Type 6417 0 23 59 in the Input box. The first number is the total, the second is the member flag, and the last two are the hours and minutes. The output should be free shipping: true and valid time: true.

C++ · editable

The output is:

free shipping: true
valid time: true

Check your logic against the samples. In sample A, free shipping is true because the total of 6417 is at least 5000. In sample B, it is false, because 550 is small and the customer is not a member. In sample C, it is true because the customer is a member, even though the total is only 1000. For the valid time, sample B fails because 24 is not a valid hour, and sample C fails because 60 is not a valid minute.

Part 3: pack the time into one number

The warehouse printer takes one number for each time. Your job is to pack hours and minutes into one int, using bits. Minutes fit in 6 bits, because 63 is the largest value 6 bits can hold, and 59 is the largest valid minute. Hours then go in the bits above, so the packed value is (hours << 6) | minutes.

Look at the brackets. The shift must happen before the OR, and << has a higher precedence than |. Brackets make the order obvious, and they cost nothing. Without them, the reader would have to check the precedence table, and a bit bug would be hard to find.

To unpack, shift right by 6 to get the hours back, and use AND with 63 to keep only the low 6 bits for the minutes. The unpacked line in the output must show the same hours and minutes that you packed. If it does not, a value is too big for its field, which is the bug that Part 4 shows.

Here is Part 3 on its own. Type 23 59 in the Input box. The output should show the packed number 1531, and it should unpack to 23 and 59.

C++ · editable

The output is:

packed: 1531
unpacked hours: 23, minutes: 59

Part 4: check your rounding and packing with small programs

The program in this part is not part of the final answer. It exists so you can see the two bugs this assignment warns about. Run each one, read the output, and then compare it with the rules above.

The first program compares the tax with and without the half-divisor trick. Its input is empty, because the values are written into the code.

C++ · editable

The output is:

without half: 419
with half: 420

Trace the first line. 5997 * 7 is 41979, and dividing by 100 drops the fraction, which leaves 419. The true tax is 419.79, so the rounding should go up to 420. Adding 50 before the division pushes any fraction of half a cent or more over the next whole cent, and then the division drops the rest. This is why the rounding rule in Part 1 is written with the 50.

The second program shows what happens when the packed minutes are too large. The input is also written into the code.

C++ · editable

The output is:

packed: 1472
unpacked hours: 23
unpacked minutes: 0

Trace the packing. The hours 23 are shifted to bits 6 and up, which gives 1472. The minute value 64 is the bit at position 6, which is exactly where the hours begin. The OR does not add the two values. It combines the bits, so the bit at position 6 is already set by the hours, and the minutes disappear. The unpacked minutes read as 0, which is wrong. This is why the minute field is limited to 63. A time with minutes 64 or more must be rejected before packing, or the packing desk gets the wrong time.

Part 5: a readable program and a short write-up

Put all parts together in one program that reads six numbers and prints the nine lines of output. Then check it against the three samples. If any line is wrong, go back to the part that produces it and check the trace again. Most mistakes at this stage come from a missing bracket, a division done too early, or a variable used before it has the value you expect.

When your program works, do three more things. First, compile it with the warnings turned on, using g++ -std=c++17 -Wall, and fix every warning. A warning about parentheses in a bit expression is the compiler telling you the precedence trap is present. Second, add a short comment above each part explaining what it does, in plain words, as if the packing desk would read it. Third, write two or three lines that say why the tax uses the 50 and why the minutes are limited to 63.

Requirements checklist

Tick each item when you have checked it yourself. Test with the three samples, not only with the one you were thinking about.

  • The program reads the six numbers in the order given above.
  • The subtotal is priceCents * quantity.
  • The tax uses integer division with the half-divisor trick, and sample A gives 420.
  • The total, the dollars and the cents match all three samples.
  • The cents are printed as a plain number, without a leading zero.
  • boolalpha is set, so the two yes-or-no lines print words.
  • Free shipping uses total >= 5000 || isMember != 0 with the right brackets.
  • The valid time uses two range checks, each joined with AND.
  • The packed value is (hours << 6) | minutes, with brackets.
  • The unpacked hours and minutes match the input for all three samples.
  • The program compiles with g++ -std=c++17 -Wall and has no warnings.
  • Each part has a comment in plain words.
  • The write-up explains the 50 in the tax and the limit of 63 on the minutes.

Grading rubric

Your work is judged on what the program does and on how well you can explain it. The three levels below describe what each score looks like.

60 percent. The program compiles and prints the right answer for at least one sample. Some lines are wrong. For example, the tax may be 419 for sample A, because the half-divisor is missing, or the free shipping test may ignore the member flag. The packing may be done, but the unpacking may not match the input. The code has no comments, or the comments do not explain the steps.

80 percent. The program prints every line correctly for all three samples. It compiles with -Wall and no warnings. Every expression that mixes bit operators, comparisons or logical operators has brackets. The valid-time check uses two range checks joined with AND. The code is readable, with a name for each intermediate value. The write-up is missing one of the two explanations.

100 percent. Everything in the 80 percent level, plus evidence that the program was tested beyond the samples. A strong answer tries a zero order (0 5 7 0 0 0), the largest minute that fits in the packing (1 1 0 0 0 63, which must unpack to 63 and print valid time: false), and a minute that does not fit (1 1 0 0 0 64, which unpacks wrongly). It explains what the packing desk should do with the minute that does not fit, and it shows that the program still prints a correct valid-time line. The write-up explains both the 50 and the 63 in plain words, using the examples from this assignment.

Background for the stretch task: the stack

The stretch task below asks you to think about how a calculator evaluates an expression with many operators. The idea depends on a stack, which you met in the last lesson. Step through the stack once more before you start the stretch task.

The top is the box marked top. An outlined box was just pushed or peeked.

The stack is empty. The last item pushed is the first one popped.

Step 1 of 8

A stack stores items in the order they arrive, and it only lets you reach the top item. Watch how each push adds a new top and each pop removes it. The item that goes in last comes out first. A calculator that understands precedence uses this order: it keeps waiting operators on a stack until an operator of equal or lower precedence arrives, and then it applies the waiting ones first.

Stretch task: describe an evaluator

This task is optional, but it shows the interview side of the module. Without writing code, describe in plain words how you would evaluate 2 + 3 * 4 - 6 / 2 using two stacks, one for numbers and one for operators. Name the stack that holds the numbers, the stack that holds the operators, and the rule you would use to decide when to apply a waiting operator. Then trace the expression step by step, writing down the contents of both stacks after each token. Check your final answer against the result of 11 from the last lesson.

A good answer uses the precedence table from the last lesson. When a * or / arrives, it does not wait, because it binds more tightly than the + or - that are already waiting. When a + or - arrives, it first applies any waiting * or /. Explain why this produces the right answer for 2 + 3 * 4 but would give a different answer if the rule ignored precedence.

Reference solution

Work through the hints first. Use the hints only when you are stuck, and try each one before opening the next. The hints are hidden, so the reference solution is hidden too.

Hint 1: the subtotal and the tax

Read the six numbers with one cin line, separated by spaces. Create subtotal as priceCents * quantity. Create tax with (subtotal * taxPercent + 50) / 100. Create total as subtotal + tax. Print the first three lines, and check them against Sample A before you go on.

Hint 2: the dollars and cents

The dollars part is total / 100. The cents part is total % 100. Print them in one line with the words dollars: and , cents: between them, as the output format shows.

Hint 3: the yes or no lines

Write cout << boolalpha; once, before the two yes-or-no lines. For free shipping, use (total >= 5000 || isMember != 0). For the valid time, make two bool variables, one for the hours and one for the minutes, and then print (validHours && validMinutes).

Hint 4: the packing

Make int packed = (hours << 6) | minutes; with the brackets. Print packed. Then unpack with packed >> 6 for the hours and packed & 63 for the minutes. Use brackets around each expression inside cout, because << and >> and & are easy to misread there.

Hint 5: testing

Run the program with each of the three samples. Compare every line, not only the total. If the unpacked line does not match the input, check the packing line first, then the mask. If the tax is one cent too small, check that you added 50 inside the bracket before the division.

The full solution is below. It is a complete program that reads the six numbers and prints the nine lines of the output. Read it only after you have tested your own version, and compare the two line by line.

Reference solution
#include <iostream>
using namespace std;
 
int main() {
    int priceCents, quantity, taxPercent, isMember, hours, minutes;
    cin >> priceCents >> quantity >> taxPercent >> isMember >> hours >> minutes;
 
    // Part 1: money in whole cents. The + 50 rounds the tax to the nearest cent.
    int subtotal = priceCents * quantity;
    int tax = (subtotal * taxPercent + 50) / 100;
    int total = subtotal + tax;
 
    cout << "subtotal: " << subtotal << endl;
    cout << "tax: " << tax << endl;
    cout << "total: " << total << endl;
    cout << "dollars: " << total / 100 << ", cents: " << total % 100 << endl;
 
    // Part 2: yes or no answers.
    cout << boolalpha;
    cout << "free shipping: " << (total >= 5000 || isMember != 0) << endl;
    bool validHours = hours >= 0 && hours <= 23;
    bool validMinutes = minutes >= 0 && minutes <= 59;
    cout << "valid time: " << (validHours && validMinutes) << endl;
 
    // Part 3: pack hours above bit 6 and minutes in the low 6 bits, then unpack.
    int packed = (hours << 6) | minutes;
    cout << "packed: " << packed << endl;
    cout << "unpacked hours: " << (packed >> 6) << ", minutes: " << (packed & 63) << endl;
    return 0;
}

For Sample A the output is the nine lines shown in the brief. The other two samples follow the same steps. A correct program prints tax: 50 for Sample B, free shipping: true for Sample C, and packed: 828 for Sample C.

What you should be able to say after this assignment

  • Why a tax in whole cents needs the half-divisor before the division.
  • Why (hours << 6) | minutes needs brackets, and what breaks when a value is too big for its bits.
  • How the range checks combine with AND, and why an input with one bad part fails the whole time.
  • What the stack in the stretch task does, and why precedence needs a second stack for waiting operators.

Keep the program and the write-up. They are the evidence for the module, and they are a good example to bring to an interview, because they show how the arithmetic, logic and bit rules fit together in one real task.

Practice this topic: coding problems for C++ Operators →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Bitwise operators and precedence