Viliam Búr

2008/08/05

Real Numbers in Java

In programming language Java there are two primitive types designed for work with real numbers: float and double. Their sizes are 4 or 8 bytes, which contain 1 sign bit, 8 or 11 exponent bits, and 23 or 52 mantissa bits. The real numbers are rounded after certain number of valid digits; that means that greater numbers have greater rounding errors. We need to make a difference between the ranges of these types (smallest and largest values) and their precisions (number of valid digits).

Each of these primitive types corresponds to an object type: java.lang.Float and java.lang.Double. Their static variables "MIN_VALUE" and "MAX_VALUE" are the smallest and the largest finite positive real numbers which can be expressed using this type.

System.out.println("Float " + Float.MIN_VALUE + " " + Float.MAX_VALUE);
// Float 1.4E-45 3.4028235E38
System.out.println("Double " + Double.MIN_VALUE + " " + Double.MAX_VALUE);
// Double 4.9E-324 1.7976931348623157E308

The formula "1.4E-45" means "1.4 × 10-45", which is 0.000 000 000 000 000 000 000 000 000 000 000 000 000 000 001 4. The formula "3.4028235E38" means "3.4028235 × 1038", which is 340 282 350 000 000 000 000 000 000 000 000 000 000; the trailing zeroes only meaning that the digits on these places have been rounded. Also the formulas "4.9E-324" and "1.7976931348623157E308" mean "4.9 × 10-324" and "1.7976931348623157 × 10308", which I will not expand, because the resulting numbers are over 300 digits long.

What happens when the result of computing is a too large or too small number, outside the limits of this type? Both real types have helper constants, which allow to return at least some results even beyond the type boundaries. Too large positive and negative numbers can be expressed by constants "POSITIVE_INFINITY" and "NEGATIVE_INFINITY". Numbers too close to zero are rounded to zero (there is also a negative zero "-0.0f", which is usually equal to normal zero, except some specific situations like division by zero). If we cannot say anything about the result, such as an infinity minus infinity or zero divided by zero, this can be expressed by a constant "NaN". (Unlike the object constant "null", these constants belong to the types "float" and "double", so they can be stored in primitive type variables too.)

If we want to find out if our calculation has returned one of these values, we can use methods "isInfinite" and "isNaN".

Why do we use these special constants for real numbers, when integer numbers in similar circumstances just overflow or throw an "ArithmeticException"? One reason is that the real numbers are rounded. If we divide by zero in an integer calculation, it usually means that the programmer has forgot to check some special case; and it is good if the program gives him/her a warning. If we divide by zero in a real calculation, it often means that we wanted to divide by some nonzero value, which was rounded to zero during the calculation. Checking for all such situations would be difficult, and essentially useless. A good programmer already knows that the real numbers are rounded, so the results of calculations are always unreliable to some degree; how much exactly can the rounding error grow depends on the specific formula. Another reason is the the way the real numbers are stored in memory allows to add these constants "naturally"; using a similar method for integer numbers would be "unnatural", and by the way it would make all programs much slower.

System.out.println(Float.MAX_VALUE * 2);  // Infinity
System.out.println(Float.MIN_VALUE / 2);  // 0.0
System.out.println(0.0f /  0.0f);  // NaN
System.out.println(1.0f /  0.0f);  // Infinity
System.out.println(1.0f / -0.0f);  // -Infinity
System.out.println(1.0f / Float.POSITIVE_INFINITY);  //  0.0
System.out.println(1.0f / Float.NEGATIVE_INFINITY);  // -0.0

(C) 2008 Viliam Búr viliambur.blogspot.com

Basic mathematical operations are done using symbols: "+" addition, "-" subtraction, "*" multiplication, "/" division. If we use both integer and real numbers, the "/" symbol between two integer numbers means integer division, between two real numbers or between a real and integer numbers it means real division.

System.out.println(1    / 3   );  // 0
System.out.println(1    / 3.0f);  // 0.333...
System.out.println(1.0f / 3   );  // 0.333...
System.out.println(1.0f / 3.0f);  // 0.333...

We can compare numbers using operators: "==" is equal, "!=" is not equal, "<" is less, ">" is more, "<=" is less or equal, ">=" is more or equal. During calculations the real numbers are rounded, so the result of "==" operator may depend on rounding errors. With real numbers we usually do not check whether they are equal, but whether their difference is in some given tolerance.

float a = 123456789f;
float b = 123000123f;
float c = 987987987f;
float x = a * b * c;
float y = c * b * a;
System.out.println(x);  // 1.5002796E25
System.out.println(y);  // 1.5002795E25

Class java.lang.Math provides many methods for calculations with integer and real numbers: absolute value "abs" and sign "signum"; smaller or greater of two numbers "min" and "max"; rounding "floor", "ceil", "round"; power "exp" and "pow", root "sqrt" and "cbrt", and logarithm "log" and "log10"; changing angles from degrees to radians "toRadians" and vice versa "toDegrees"; goniometric functions "sin", "cos", "tan", "asin", "acos", "atan", "atan2" and hyperbolic functions "sinh", "cosh", "tanh". It provides also mathematical constants "E" and "PI".

There is also a class java.lang.StrictMath, which provides the same methods, with additional guarantee that these methods will give exactly the same results when used on any platform.

We can receive random numbers using object java.util.Random. Methods "nextFloat" and "nextDouble" return random real numbers distributed uniformly between 0 and 1. Method "nextGaussian" returns random real numbers according to Gaussian normal distribution with mean 0 and standard deviation 1; this means that approximately 68% of values will not be further from the mean (in any direction) than the standard deviation, 95% of values will not be further than two standard deviations, and 99.7% of values will not be further than three standard deviations. For example the IQ tests have mean 100 and standard deviation 15, so here is a simulator of IQ tests of randomly selected individuals in population:

java.util.Random r = new java.util.Random();
for (int i = 0; i < 100; i++) {
  System.out.println(Math.round(100 + 15 * r.nextGaussian()));
}

Related articles:

Labels:

2008/08/03

Integer Numbers in Java

In programming language Java there are four primitive types designed for work with integers: byte, short, int, and long. These are signed integer numbers with size 1, 2, 4, 8 bytes, that is 8, 16, 32, 64 bits. (Also char type designed for work with characters is a 16-bit unsigned integer, and we can use it for mathematical operations, but for the sake of program legibility I do not recommend doing this needlessly.)

Basic mathematical operations are done using symbols: "+" addition, "-" subtraction, "*" multiplication, "/" integer division, "%" integer division reminder.

int a = 100;
int b = 6;

System.out.println(a + b);  // 106
System.out.println(a - b);  //  94
System.out.println(a * b);  // 600
System.out.println(a / b);  //  16
System.out.println(a % b);  //   4

The "+" symbol means not only addition but also string concatenation, and it has the same priority as mathematical "+" and "-". If you display calculated results accompanied by text description, take care about the operator priority, and use parentheses around the calculation.

int x = 2;
int y = 3;

System.out.println("Sum is " + x + y);    // Sum is 23
System.out.println("Sum is " + (x + y));  // Sum is 5

We can compare numbers using operators: "==" is equal, "!=" is not equal, "<" is less, ">" is more, "<=" is less or equal, ">=" is more or equal.

int x = 2;
int y = 3;

if (x == y) {
 System.out.println("X is equal to Y");
}
if (x != y) {
 System.out.println("X is not equal to Y");
}
if (x < y) {
 System.out.println("X is less than Y");
}
if (x > y) {
 System.out.println("X is more than Y");
}
if (x <= y) {
 System.out.println("X is less than or equal to Y");
}
if (x >= y) {
 System.out.println("X is more than or equal to Y");
}

Each of these primitive types corresponds to an object type: java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long; these types are subtypes of abstract type java.lang.Number. Using object types we can use integers in contexts where objects are required, such as sets or lists. The object types provide also some useful static methods. A primitive value can be converted to object using static method valueOf; an object can be converted to primitive value using methods byteValue, shortValue, intValue, longValue.

int i = 5;
Integer j = Integer.valueOf(i);
int k = j.intValue();

System.out.println(i);  // 5
System.out.println(j);  // 5
System.out.println(k);  // 5

Java supports autoboxing, that is automatical insertion of methods converting primitive values to objects and vice versa. The previous example could have been written more simply, but it would be the same thing.

int i = 5;
Integer j = i;
int k = j;

System.out.println(i);  // 5
System.out.println(j);  // 5
System.out.println(k);  // 5

Be careful about autoboxing. It is better to use the methods explicitly, because it helps us avoid errors which are very difficult to spot. An "Integer" variable can contain null value, an "int" variable always contains some number; if we forget this, the automatic conversion may throw a NullPointerException.

When using "Integer" and "int" types the meaning of "==" symbol is different. For primitive types the "==" symbol compares the numbers. For object types the "==" symbol tests if it is the same object instance. So if we have two different objects containing the same number, the "==" symbol will return false, because they are different; to compare their contents we have to use "equals" method. With two different primitive types, for example "int" and "long", the "==" symbol compares if they contain the same number; for "Integer" and "Long" objects the "equals" method always returns false. When using objects for numbers, we have to be very careful about what exactly are we comparing.

(C) 2008 Viliam Búr viliambur.blogspot.com

int i = 5;
int j = 5;
if (i == j) {  // yes
 System.out.println("I and J are equal");
}

Integer I = new Integer(5);
Integer J = new Integer(5);
if (I == J) {  // no
 System.out.println("I and J are the same object");
}
if (I.equals(J)) {  // yes
 System.out.println("I is same as J");
}

int x = 5;
long y = 5;
if (x == y) {  // yes
 System.out.println("X and Y are equal");
}

Number X = new Integer(5);
Number Y = new Long(5);
if (X == Y) {  // no
 System.out.println("X and Y are the same object");
}
if (X.equals(Y)) {  // no!
 System.out.println("X is same as Y");
}
if (X.longValue() == Y.longValue()) {  // yes
 System.out.println("X and Y are equal");
}

We can calculate the range of each integer type from their number of bytes, or we can use their static variables "MIN_VALUE" and "MAX_VALUE".

System.out.println("Byte " + Byte.MIN_VALUE + " " + Byte.MAX_VALUE);
// Byte -128 127
System.out.println("Short " + Short.MIN_VALUE + " " + Short.MAX_VALUE);
// Short -32768 32767
System.out.println("Integer " + Integer.MIN_VALUE + " " + Integer.MAX_VALUE);
// Integer -2147483648 2147483647
System.out.println("Long " + Long.MIN_VALUE + " " + Long.MAX_VALUE);
// Long -9223372036854775808 9223372036854775807

If we want to write the number into string, we can use the "toString" method (it has also a static version) or use the "+" symbol to append the number to a string (for example, an empty string). If we want to use other than decimal numeral system, we can use static method "toString" with two parameters. The numeral system base cannot be larger than "Character.MAX_RADIX", but we usually need bases from 2 to 16, and these are allright. For frequently used bases there are also static methods "toBinaryString", "toOctalString", "toHexString" which convert an unsigned number as a parameter.

int x = 123456789;
System.out.println(Integer.toHexString(x));   //  75bcd15
System.out.println(Integer.toString(x, 16));  //  75bcd15

int y = -123456789;
System.out.println(Integer.toHexString(y));   // f8a432eb
System.out.println(Integer.toString(y, 16));  // -75bcd15

If we want to read number from a string, we can use static method "parseInt" ("parseByte",...) or "valueOf", depending on whether we need a primitive type or an object. (If the string contains unsigned number, for example returned by method "toHexString", there can be a range overflow and a "NumberFormatException".)

System.out.println(Integer.parseInt("cafe", 16));  // 51966

We can also perfrom bit operations on integers: "&" conjunction (both bits), "|" disjunction (at least one bit), "^" exclusive disjunction (exactly one bit). We can shift bits using operators: "<<" left shift, ">>" sign preserving right shift, ">>>" zero filling right shift.

int i = Integer.parseInt("0011", 2);
int j = Integer.parseInt("0101", 2);

System.out.println(Integer.toBinaryString(i & j));  // 0001
System.out.println(Integer.toBinaryString(i | j));  // 0111
System.out.println(Integer.toBinaryString(i ^ j));  // 0110

int k = -100;

System.out.println(Integer.toBinaryString(k));
// 11111111111111111111111110011100
System.out.println(Integer.toBinaryString(k << 1));
// 11111111111111111111111100111000
System.out.println(Integer.toBinaryString(k >> 1));
// 11111111111111111111111111001110
System.out.println(Integer.toBinaryString(k >>> 1));
// 01111111111111111111111111001110

We can receive random numbers using object java.util.Random. Here is a dice throwing simulation:

java.util.Random r = new java.util.Random();
for (int i = 0; i < 100; i++) {
 System.out.println(1 + r.nextInt(6));
}

Related articles:

Labels: