☰ Contents Java Details

Primitives: The Full `cast`

Student: I’m looking at some other methods now. What’s going on in Px.of? Two weird things – what’s isNaN/isInfinite, and what’s the (int) at the bottom?
  static int of(double v) {
      if (Double.isNaN(v) || Double.isInfinite(v)) {
          throw new IllegalArgumentException("measurement must be a number, got " + v);
      }
      return (int) Math.round(v);
  }

Pict.java, lines 82–87

Prof: Great questions both. When double arithmetic was designed, a decision was made to have no errors be reported from it. So unlike 1 / 0 on ints, which reports an error, 1 / 0 evaluates to Infinity, which is a valid double value. Similarly, things like Math.sqrt(-1) don’t have a mathematical answer in terms of a double. These produce a special NaN (stands for “Not a Number”) value. The check here is making sure that the value is not one of those special nonsense values before converting it to an int.
Student: Hmm OK. That’s a lot but I kind of see. And then what about (int) – you said that’s converting to an int?
Prof: Right, (int)v is called a cast, which turns one primitive type into another. (There are other types of casts, but this is the one on primitives). Casting is actually super complex and fraught. The simple explanation is that it drops the decimal part of the double to make an int. It’s very useful because it allows us to go back and forth between the primitive types.
Student: What’s so hard about that?
Prof: The problem is that int can only represent numbers up to about 2^31, while double can reprsent much bigger numbers than that. So consider this weird example:
Student: Huh? They’re clearly not the same.
Prof: Right, so casting “clamps” for the double to int conversion. That number is the biggest int, and it’s returned for any cast of a double larger than that.
Student: Weird. Do I have to care?
Prof: Kind of! In the Pict library these casts are used for pixels meant to be displayed on screen, so having a width of billions of them doesn’t make much sense. But in many applications we do have billions of things, like cents. So this can matter a lot, so it’s a choice to convert. There’s no easy answer either, you have to think about your data and what operations happen on it.
Student: Hmmm, that seems important and also very hard.

Prof: It leads to a lot of mistakes in real code. Keep it in mind. OK so while we are on the topic I want to mention all the primitive types:

  • byte: whole numbers from −128 to 127
  • short: whole numbers from −32,768 to 32,767
  • int: whole numbers from −2,147,483,648 to 2,147,483,647 (about ±2 billion)
  • long: whole numbers up to about ±9.2 × 1018
  • float: decimal numbers up to about ±3.4 × 1038
  • double: decimal numbers up to about ±1.8 × 10308
  • char: a single character, like 'a'
  • boolean: true or false

It’s good to know all of these for reading code.

Student: So can I cast between any of them?
Prof: In general casting from a smaller type to a bigger type works pretty well – int to double for example, because double is bigger. Casting from bigger to smaller, or between same-sized values, can lose information. So it’s important to look up the real rules, test, and so on when doing it.

Practice

What does each expression evaluate to? (Write a double with its .0.)
(int) 3.99 is
(int) -3.99 is
(double) 7 / 2 is
(double) (7 / 2) is
(int) 1e10 is
(int) Math.round(2.5) is
Which of these does Java make you write a cast for? Select all that need one.
  1. double to int
    A double can have a decimal part that an int can’t hold, so Java makes you say (int).
  2. long to int
    A long can be far bigger than any int, so Java makes you say (int).
  3. int to double
    Every int fits in a double, so Java converts it without being asked, as in 7 / 2.0.
  4. int to long
    Every int fits in a long, so no cast is needed.