Java Data Types - Numeric Data
Written by Ian Elliot   
Article Index
Java Data Types - Numeric Data
Literals and Expressions
Numeric Output

Literals

In most cases we get numeric values into variables by initializing them using a literal - i.e. data that you write out.

For example in:

int a=12345;

12345 is a numeric literal.

To be able to specify things correctly we need to specify the type of the literal

If you just type an integer it is assumed to be an int

If you end the an integer with an L then it assumed to be a long.

You can also specify integers in a number of different number bases. If you start a number off with a 0x then it is taken to be in hex and if you start it with 0b then it is binary. For example 0xA is ten in hex and 0b11 is three in binary.

For floating point numbers we have two type of literal. If you just type a value with a decimal point, 123.4 then is assumed to be a double. To specify a float you have to add a trailing f as in 123.4f.or to explicitly specify a double you can use a trailing d as in 123.4d

You can also specify floating point numbers using scientific notation. For example, 1E17.

Summary

  • There are two integer literals - int the default and long indicated by a trailing L as in 123L.
  • You can enter literals using decimal, the default, or hex, indicated by a leading 0x or binary, indicated by a leading 0b.
  • There are two floating point literals - the default double indicated by a trailing d e.g. 123,4d and float indicated by a trailing f e.g. 123.4f.
  • You can use scientific notation for floating point numbers using E to indicate the exponent.

Using Literals

When you initialize a variable you don't have to bother with setting the type of the literal - it is assumed to be the same type as the variable. Initialization always works unless you specify a value that is too big.

For example

byte myByte=42;

works even though the literal is by default an int. However if you do specify a type then you will see an error message:

byte myByte=42L;

For a long you can write either

long myLong=123;

or

long myLong=123L;

What this means is that the only time you need to use a long literal is when the value is too big to represent as an int.

When it comes to floating point the situation is a little strange. If you use:

float myFloat=123;

then it all works - the int is converted to a float. However if you use:

float myFloat=123.0;

you will see an error that says you are losing precision due to the conversion of a double to a float.

Any literal with a decimal point is assumed to be a double.

float myFloat=123.0f;

works.

Summary

  • When initializing a variable you generally only have to worry about long integers (when the value is too big for an int) and float.

Expressions

Of course usually the whole point of numbers is to do arithmetic. There isn't much to say about arithmetic expressions in general. The operators that you can use should be familiar to you from the chapter Java - Command Line Programs

  • + Additive operator
  • - Subtraction operator
  • * Multiplication operator
  • / Division operator

Notice that there is no raise to a power operator. If you want to raise a value to a power you have to use the Math class and its pow method which takes two doubles and raises the first to the second. For example:

double x=10.2;
double result=Math.pow(x,2.0);

will square the contents of x.

It is also worth noting that the division operator works at the level of the most precise type in the expression. That is

int myInt=3;
int result=myint/2;

stores 1 in result because this is integer division and any fractional parts are simply ignored. However

int myInt=3;
double result=myint/2.0;

attempts to do a floating point division and automatically converts myint to double and produces the answer 1.5.

Arithmetic with integers can become more complicated with respect to type.  The problem for integer arithmetic is that all arithmetic is by default done as int unless there is a long involved in the expression when it is done as long.

Let's look at an example and see how this can complicate things.

If you write:

byte myByte=10;
myByte=10+10;

Then everything works as you would expect and 20 is stored in myByte. However if you try the superficially similar:

byte myByte=10;
myByte=myByte+10;

The result is an error about possible loss of precision because the right hand side is an int.

The rule is that any expression that involves a variable is converted to an int and the problem occurs when you attempt to store the int back in the byte.

if you are writing arithmetic that involves only ints and are assigning to either an int or a long this is no problem as there is no loss of precision. If you are assigning to a byte or a short then you get an error, even if the expression involves only bytes or shorts. For example:

short myShort=10;
myShort=myShort+myShort;

generates an error because you are about to store an int in a short.

This loss of precision problem doesn't occur with floating point values because arithmetic is done either as float or double depending on the most precise type that occurs in the expression.

Widening and Narrowing

When it comes to data types there are two possible types of assignment - widening and narrowing.

A widening assignment causes no problem because the variable on the left is "bigger" than what is being computed on the right and so can be stored without loss of precision.

For example you can always do:

myDouble=myFloat;

or

myInt=myByte;

A narrowing assignment is a potential problem because the variable on the left isn't always capable of storing the result on the right.

For example

myFloat=myDouble;

or

myByte=myInt;

Things might work ok if say the value stored in myInt is small enough to be stored in myByte - but equally it might not.

Widening assignments are performed by the system without you getting involved - and this is a general principle that goes beyond just numbers.

A narrowing assignment needs some help and it generally requires the programmer to explicitly specify how the types should be converted.

Casting

So suppose we do want to assign an int to a byte how do we do it?

The answer is to use a cast and this is again a more general mechanism than it appears. You can attempt to convert any type to any type with a cast but it doesn't always work or make sense!

To cast one type to another you simply use

(type) variable

For example:

int myInt=10;
byte myByte;
myByte=(byte)myInt;

This converts the value in myInt to a byte and stores it in myByte.

Simple and brutal.

If the int is actually in the range that a byte can represent then it works fine. If it isn't then all you get are the bottom eight bits of the 16 bit value. This often makes no sense numerically but some types of bit manipulation rely on it.

Unless you are doing bit manipulation casting a numerical value to a "smaller" type only makes sense if the value can be accurately represented by the smaller value.

So for example you can safely use casting to make byte arithmetic "safe" as in

byte myByte=10;
myByte=(byte)(myByte+myByte);

Now even though the arithmetic promotes the byte to an int it is converted back again to a byte by the cast.

Notice that it doesn't take much to cause a cast to do something that you might not want. For example:

byte myByte=127;
myByte=(byte)(myByte+myByte);

produces the answer -2. This makes sense if you know how the numbers are stored in binary signed format but unless you are doing some advanced bit manipulation this isn't particularly useful.

The same sorts of things occur if you cast a float or a double to int or an integer type. For example:

double myDouble=1.5;
int myInt=(int) myDouble;

in this case the cast simply truncates the result - it throws way the fractional part to give you one.

What if you want to perform a more gentle and accurate sort of conversion?

The answer is to use the Math class and its numeric conversion methods all of which accept a double and return a double as the result:

  • ceil returns the smallest integer  not less than the specified double
  • floor returns the largest integer not greater than the specified double.
  • rint returns the integer that is closest to the specified double

There is also a round method which will accept an double or a float and return the closes integer as a long or an int respectively. 

For example:

int myInt=(int) Math.floor(1.5);

stores 1 in myInt - notice you still need a cast as floor returns a double.

Similarly:

int myInt=(int) Math.ceil(1.5);

stores 2 in myInt.

There are lots of other useful mathematical methods included in the Math class and it is worth finding out more about.