Topics › Basics › Things to Know in C++/Java/Python or any language

Cpp Basics

Easy TUF ID 1211 A2Z

What this covers

This row of the sheet is called “Cpp Basics” because the original course was taught in C++. The ideas are language-independent, so here they are in Java: the primitive types and their ranges, what happens when a value no longer fits, how division, remainder and casting behave, why characters are really small integers, why 0.1 + 0.2 is not 0.3, and the one surprise hidden inside boxed Integers. Almost every “works on the sample, fails on the hidden test” bug in later problems traces back to one of these.

The primitive types

Java has eight primitive types. The ones that matter for problem solving are:

Type Size Range Use it for
int 32 bits about -2.1 × 10^9 to 2.1 × 10^9 counts, indices, most values
long 64 bits about -9.2 × 10^18 to 9.2 × 10^18 sums, products, anything above 10^9
double 64 bits about 15 significant digits measurements, averages
char 16 bits 0 to 65535, unsigned single characters
boolean 1 value true or false conditions

byte (8 bits), short (16 bits) and float (32 bits) exist but almost never appear in solutions. The exact limits live in the wrapper classes: Integer.MAX_VALUE is 2147483647, Long.MAX_VALUE is 9223372036854775807. A literal that must be a long needs an L suffix: 3_000_000_000L compiles, 3_000_000_000 does not, because the compiler reads an unsuffixed literal as an int. Underscores inside literals are allowed and make large numbers readable.

Overflow is silent

When an int operation produces a result outside its range, Java does not throw. It keeps the low 32 bits, which means the value wraps around.

static int addInt(int a, int b) {
    return a + b;
}

addInt(Integer.MAX_VALUE, 1) returns Integer.MIN_VALUE, a large negative number. The fix is to make at least one operand a long before the operation, so the arithmetic happens in 64 bits:

static long addLong(int a, int b) {
    return (long) a + b;
}

The cast applies to a alone, then b is widened to match, and the addition is done in long. Writing (long) (a + b) would be useless: the overflow has already happened inside the parentheses. The factorial example makes the stakes concrete:

static int factorialInt(int n) {
    int f = 1;
    for (int i = 2; i <= n; i++) f *= i;
    return f;
}
static long factorialLong(int n) {
    long f = 1;
    for (int i = 2; i <= n; i++) f *= i;
    return f;
}

12! is 479001600 and fits. 13! is 6227020800, and the int version returns 1932053504, a perfectly plausible-looking wrong answer. The long version is exact up to 20!. Beyond that even long wraps, and problems then ask for the answer modulo 10^9 + 7.

Integer division and remainder

/ on two integers throws away the fractional part, always toward zero. % returns what is left over and takes the sign of the left operand.

static int[] divMod(int a, int b) {
    return new int[]{a / b, a % b};
}

divMod(-7, 2) is {-3, -1}, not {-4, 1}. This matters whenever you use % to wrap an index or take a number modulo m: a negative input gives a negative result, and array[-1] throws. Math.floorDiv and Math.floorMod round toward negative infinity instead, so floorMod(x, m) is always in [0, m) for positive m:

static int[] floorDivMod(int a, int b) {
    return new int[]{Math.floorDiv(a, b), Math.floorMod(a, b)};
}

The same truncation bites averages. (a + b) / 2 with a = 3, b = 4 is 3, because both operands are int. One double operand changes the whole expression to real division:

static double average(int a, int b) {
    return (a + b) / 2.0;
}

Casting and rounding

Converting a wider type to a narrower one requires an explicit cast and may lose information. (int) 3.99 is 3; the fraction is dropped, again toward zero, so (int) -3.99 is -3. Math.round goes to the nearest whole number instead, and on a tie it rounds toward positive infinity: Math.round(2.5) is 3 and Math.round(-2.5) is -2.

static long[] truncateAndRound(double x) {
    return new long[]{(int) x, Math.round(x)};
}

Narrowing between integer types keeps the low bits: (byte) 200 is -56, (int) 3_000_000_000L is -1294967296. Widening (int to long, int to double) is automatic and safe, except that a long above 2^53 loses precision when converted to double.

Characters are numbers

A char holds a Unicode code unit, and arithmetic on it produces an int. 'a' is 97, 'A' is 65, '0' is 48. This gives two indispensable idioms. The first turns a letter into an offset and back:

static char shiftLowercase(char c, int k) {
    return (char) ('a' + (c - 'a' + k) % 26);
}

c - 'a' is 0 for 'a' and 25 for 'z'; adding k and taking % 26 wraps around the alphabet; adding 'a' back and casting to char gives a letter again. The cast is required because 'a' + int is an int. The second idiom reads a digit character:

static int digitValue(char c) {
    return c - '0';
}

'7' - '0' is 7. Using '7' directly as a number gives 55, a classic bug when summing digits of a string. Character.getNumericValue('7') returns the same 7, but the subtraction is faster and makes the intent obvious.

Floating point is approximate

double stores binary fractions, and 0.1 has no finite binary representation, so 0.1 + 0.2 evaluates to 0.30000000000000004. Comparing doubles with == is therefore wrong in general; compare the difference against a small tolerance:

static boolean nearlyEqual(double a, double b) {
    return Math.abs(a - b) < 1e-9;
}

Better still, avoid double when the problem is really about integers. Checking whether n is a perfect square by computing Math.sqrt(n) and testing == (int) sqrt is fragile for large n; squaring an integer candidate in long is exact.

Boxed integers and ==

Collections such as ArrayList<Integer> cannot hold primitives, so Java wraps values in Integer objects. Comparing two of them with == compares references. Values from -128 to 127 are cached and share one object, so == happens to work; outside that range each boxing creates a new object and == is false even for equal values.

static boolean[] boxedComparison(int v) {
    Integer a = v, b = v;
    return new boolean[]{a == b, a.equals(b)};
}

boxedComparison(127) returns {true, true} and boxedComparison(128) returns {false, true}. Always use equals (or unbox to int) when comparing wrapper objects. The same rule applies to String: == asks “same object?”, equals asks “same characters?”.

Edge cases and pitfalls