Numbers are basic to just about any computation. They’re used for array indices, temperatures, salaries, ratings, and an infinite variety of things. Yet they’re not as simple as they seem. With floating-point numbers, how accurate is accurate? With random numbers, how random is random? With strings that should contain a number, what actually constitutes a number?
Java has several built-in or “primitive” types that can be used to represent numbers, summarized in Table 5-1 with their “wrapper” (object) types, as well as some numeric types that do not represent primitive types. Note that unlike languages such as C or Perl, which don’t specify the size or precision of numeric types, Java—with its goal of portability—specifies these exactly and states that they are the same on all platforms.
Built-in type | Object wrapper | Size of built-in (bits) | Contents |
---|---|---|---|
|
|
8 |
Signed integer |
|
|
16 |
Signed integer |
|
|
32 |
Signed integer |
|
|
64 |
Signed integer |
|
|
32 |
IEEE-754 floating point |
|
|
64 |
IEEE-754 floating point |
|
|
16 |
Unsigned Unicode character |
n/a |
BigInteger |
unlimited |
Arbitrary-size immutable integer value |
n/a |
BigDecimal |
unlimited |
Arbitrary-size-and-precision immutable floating-point value |
As you can see, Java provides a numeric type for just about any purpose. There are four sizes of signed integers for representing various sizes of whole numbers. There are two sizes of floating-point numbers to approximate real numbers. There is also a type specifically designed to represent and allow operations on Unicode characters. The primitive numeric types are discussed here. The “Big” value types are described in Recipe 5.12.
When you read a string representing a number from user input or a text file, you need to convert it to the appropriate type. The object wrapper classes in the second column have several functions, one of which is to provide this basic conversion functionality—replacing the C programmer’s atoi/atof family of functions and the numeric arguments to scanf.
Going the other way, you can convert any number (indeed, anything at all in Java) to a string just by using string concatenation. If you want a little bit of control over numeric formatting, Recipe 5.5 shows you how to use some of the object wrappers’ conversion routines. And if you want full control, that recipe also shows the use of NumberFormat
and its related classes to provide full control of formatting.
As the name object wrapper implies, these classes are also used to “wrap” a number in a Java object, as many parts of the standard API are defined in terms of objects. Later on, “Solution”
shows using an Integer
object to save an int
’s value to a file using object serialization, and retrieving the value later.
But I haven’t yet mentioned the issues of floating point. Real numbers, you may recall, are numbers with a fractional part. There is an infinite number of real numbers. A floating-point number—what a computer uses to approximate a real number—is not the same as a real number. The number of floating-point numbers is finite, with only 2^32 different bit patterns for float
s, and 2^64 for double
s. Thus, most real values have only an approximate correspondence to floating point. The result of printing the real number 0.3 works correctly, as in:
// numbers/RealValues.java
System
.
out
.
println
(
"The real value 0.3 is "
+
0.3
);
results in this printout:
The real value 0.3 is 0.3
But the difference between a real value and its floating-point approximation can accumulate if the value is used in a computation; this is often called a rounding error. Continuing the previous example, the real 0.3 multiplied by 3 yields:
The real 0.3 times 3 is 0.89999999999999991
Surprised? Not only is it off by a bit from what you might expect, you will of course get the same output on any conforming Java implementation. I ran it on machines as disparate as an AMD/Intel PC with OpenBSD, a PC with Windows and the standard JDK, and on Mac OS X. Always the same answer.
And what about random numbers? How random are they? You have probably heard the expression
“pseudorandom number generator, or PRNG.” All conventional random number generators,
whether written in Fortran, C, or Java, generate pseudo-random numbers. That is, they’re
not truly random! True randomness comes only from specially built hardware: an analog
source of Brownian noise connected to an analog-to-digital converter, for
example.1 Your average PC of today may have some good sources of entropy, or even hardware-based sources of
randomness (which have not been widely used or tested yet). However, pseudorandom number
generators are good enough for most purposes, so we use them. Java provides one random
generator in the base library java.lang.Math
, and several others; we’ll examine these in
Recipe 5.9.
The class java.lang.Math
contains an entire “math library” in one class, including trigonometry, conversions (including degrees to radians and back), rounding, truncating, square root, minimum, and maximum. It’s all there. Check the javadoc for java.lang.Math
.
The package java.math
contains support for “big numbers”—those larger than the normal built-in long integers, for example. See Recipe 5.12.
Java works hard to ensure that your programs are reliable. The usual ways you’d notice this are in the common requirement to catch potential exceptions—all through the Java API—and in the need to “cast” or convert when storing a value that might or might not fit into the variable you’re trying to store it in. I’ll show examples of these.
Overall, Java’s handling of numeric data fits well with the ideals of portability, reliability, and ease of programming.
The Java Language Specification. The javadoc page for java.lang.Math
.
You need to check whether a given string contains a valid number, and, if so, convert it to binary (internal) form.
To accomplish this, use the appropriate wrapper class’s conversion routine and catch the NumberFormatException
. This code converts a string to a double
:
public
static
void
main
(
String
[]
argv
)
{
String
aNumber
=
argv
[
0
];
// not argv[1]
double
result
;
try
{
result
=
Double
.
parseDouble
(
aNumber
);
System
.
out
.
println
(
"Number is "
+
result
);
}
catch
(
NumberFormatException
exc
)
{
System
.
out
.
println
(
"Invalid number "
+
aNumber
);
return
;
}
}
Of course, that lets you validate only numbers in the format that the designers of the wrapper classes expected. If you need to accept a different definition of numbers, you could use regular expressions (see Chapter 4) to make the determination.
There may also be times when you want to tell if a given number is an integer number or a floating-point number. One way is to check for the characters ., d
, e
, or f
in the input; if one of these characters is present, convert the number as a double
. Otherwise, convert it as an int
:
/*
* Process one String, returning it as a Number subclass
*/
public
static
Number
process
(
String
s
)
{
if
(
s
.
matches
(
"[+-]*\d*\.\d+[dDeEfF]*"
))
{
try
{
double
dValue
=
Double
.
parseDouble
(
s
);
System
.
out
.
println
(
"It's a double: "
+
dValue
);
return
Double
.
valueOf
(
dValue
);
}
catch
(
NumberFormatException
e
)
{
System
.
out
.
println
(
"Invalid double: "
+
s
);
return
Double
.
NaN
;
}
}
else
// did not contain . d e or f, so try as int.
try
{
int
iValue
=
Integer
.
parseInt
(
s
);
System
.
out
.
println
(
"It's an int: "
+
iValue
);
return
Integer
.
valueOf
(
iValue
);
}
catch
(
NumberFormatException
e2
)
{
System
.
out
.
println
(
"Not a number: "
+
s
);
return
Double
.
NaN
;
}
}
A more involved form of parsing is offered by the DecimalFormat
class, discussed in Recipe 5.5.
There is also the Scanner
class; see Recipe 10.6.
You need to convert numbers to objects and objects to numbers.
Use the Object Wrapper classes listed in Table 5-1 at the beginning of this chapter.
Often you have a primitive number and you need to pass it into a method where an Object
is required, or vice versa. Long ago you had to invoke the conversion routines that are part of the wrapper
classes, but now you can generally use automatic conversion (called “autoboxing"/"autounboxing”).
See Example 5-1 for examples of both.
public
class
AutoboxDemo
{
/** Shows autoboxing (in the call to foo(i), i is wrapped automatically) * and auto-unboxing (the return value is automatically unwrapped). */
public
static
void
main
(
String
[
]
args
)
{
int
i
=
42
;
int
result
=
foo
(
i
)
;
System
.
out
.
println
(
result
)
;
}
public
static
Integer
foo
(
Integer
i
)
{
System
.
out
.
println
(
"Object = "
+
i
)
;
return
Integer
.
valueOf
(
123
)
;
}
}
Autoboxing: int 42
is converted to Integer(42)
. Also auto-unboxing: the Integer
returned from foo()
is auto-unboxed to assign to int result
.
No Auto-boxing: valueOf()
returns Integer
. If the line said return Integer.intValueOf(123)
then it would be a second example of auto-boxing because the method return value is Integer
.
To explicitly convert between an int
and an Integer
object, or vice versa, you can use the
wrapper
class methods:
public
class
IntObject
{
public
static
void
main
(
String
[]
args
)
{
// int to Integer
Integer
i1
=
Integer
.
valueOf
(
42
);
System
.
out
.
println
(
i1
.
toString
());
// or just i1
// Integer to int
int
i2
=
i1
.
intValue
();
System
.
out
.
println
(
i2
);
}
}
You want to multiply an integer by a fraction without converting the fraction to a floating-point number.
Multiply the integer by the numerator and divide by the denominator.
This technique should be used only when efficiency is more important than clarity because it tends to detract from the readability—and therefore the maintainability—of your code.
Because integers and floating-point numbers are stored differently, it may sometimes be desirable and feasible, for efficiency purposes, to multiply an integer by a fractional value without converting the values to floating point and back, and without requiring a “cast”:
public
class
FractMult
{
public
static
void
main
(
String
[]
u
)
{
double
d1
=
0.666
*
5
;
// fast but obscure and inaccurate: convert
System
.
out
.
println
(
d1
);
// 2/3 to 0.666 in programmer's head
double
d2
=
2
/
3
*
5
;
// wrong answer - 2/3 == 0, 0*5 = 0
System
.
out
.
println
(
d2
);
double
d3
=
2
d
/
3
d
*
5
;
// "normal"
System
.
out
.
println
(
d3
);
double
d4
=
(
2
*
5
)/
3
d
;
// one step done as integers, almost same answer
System
.
out
.
println
(
d4
);
int
i5
=
2
*
5
/
3
;
// fast, approximate integer answer
System
.
out
.
println
(
i5
);
}
}
Running it looks like this:
$ java numbers.FractMult 3.33 0.0 3.333333333333333 3.3333333333333335 3 $
You should also beware of the possibility of numeric overflow, and avoid this optimization if you cannot guarantee that the multiplication by the numerator will not overflow.
You want to know if a floating-point computation generated a sensible result, as well as comparing and rounding floating-point values.
Compare with the INFINITY
constants, and use isNaN()
to check for “not a number.”
Compare floating values with an “epsilon” value.
Round floating point values with Math.round()
or custom code.
Comparisons can be a bit tricky: Fixed-point operations that can do things like divide by zero result in Java notifying you abruptly by throwing an exception. This is because integer division by zero is considered a logic error.
Floating-point operations, however, do not throw an exception because they are defined over an (almost) infinite range of values. Instead, they signal errors by producing the constant POSITIVE_INFINITY
if you divide a positive floating-point number by zero, the constant NEGATIVE_INFINITY
if you divide a negative floating-point value by zero, and NaN
(Not a Number) if you otherwise generate an invalid result. Values for these three public constants are defined in both the Float
and the Double
wrapper classes. The value NaN
has the unusual property that it is not equal to itself (i.e., NaN
!= NaN
). Thus, it would hardly make sense to compare a (possibly suspect) number against NaN
, because the following expression can never be true:
x == NaN
Instead, the methods Float.isNaN(float)
and Double.isNaN(double)
must be used:
public
static
void
main
(
String
[]
argv
)
{
double
d
=
123
;
double
e
=
0
;
if
(
d
/
e
==
Double
.
POSITIVE_INFINITY
)
System
.
out
.
println
(
"Check for POSITIVE_INFINITY works"
);
double
s
=
Math
.
sqrt
(-
1
);
if
(
s
==
Double
.
NaN
)
System
.
out
.
println
(
"Comparison with NaN incorrectly returns true"
);
if
(
Double
.
isNaN
(
s
))
System
.
out
.
println
(
"Double.isNaN() correctly returns true"
);
}
Note that this, by itself, is not sufficient to ensure that floating-point calculations have been done with adequate accuracy. For example, the following program demonstrates a contrived calculation—Heron’s formula for the area of a triangle—both in float
and in double
. The double values are correct, but the floating-point value comes out as zero due to rounding errors. This happens because, in Java, operations involving only float
values are performed as 32-bit calculations. Related languages such as C automatically promote these to double during the computation, which can eliminate some loss of accuracy. Let’s take a look:
public
class
Heron
{
public
static
void
main
(
String
[]
args
)
{
// Sides for triangle in float
float
af
,
bf
,
cf
;
float
sf
,
areaf
;
// Ditto in double
double
ad
,
bd
,
cd
;
double
sd
,
aread
;
// Area of triangle in float
af
=
12345679.0f
;
bf
=
12345678.0f
;
cf
=
1.01233995f
;
sf
=
(
af
+
bf
+
cf
)/
2.0f
;
areaf
=
(
float
)
Math
.
sqrt
(
sf
*
(
sf
-
af
)
*
(
sf
-
bf
)
*
(
sf
-
cf
));
System
.
out
.
println
(
"Single precision: "
+
areaf
);
// Area of triangle in double
ad
=
12345679.0
;
bd
=
12345678.0
;
cd
=
1.01233995
;
sd
=
(
ad
+
bd
+
cd
)/
2.0d
;
aread
=
Math
.
sqrt
(
sd
*
(
sd
-
ad
)
*
(
sd
-
bd
)
*
(
sd
-
cd
));
System
.
out
.
println
(
"Double precision: "
+
aread
);
}
}
Now let’s run it.
$ java numbers.Heron Single precision: 0.0 Double precision: 972730.0557076167
If in doubt, use double
!
To ensure consistency of very large magnitude double computations on different Java implementations, Java provides the keyword strictfp
, which can apply to classes, interfaces, or methods within a class.2 If a computation is Strict-FP, then it must always, for example, return the value INFINITY
if a calculation would overflow the value of Double.MAX_VALUE
(or underflow the value Double.MIN_VALUE
). Non-Strict-FP calculations—the default—are allowed to perform calculations on a greater range and can return a valid final result that is in range even if the interim product was out of range. This is pretty esoteric and affects only computations that approach the bounds of what fits into a double.
Based on what we’ve just discussed, you probably won’t just go comparing two floats or doubles for equality. You might expect the floating-point wrapper classes, Float
and Double
, to override the equals()
method, which they do. The equals()
method returns true
if the two values are the same bit for bit (i.e., if and only if the numbers are the same or are both NaN
). It returns false
otherwise, including if the argument passed in is null, or if one object is +0.0 and the other is –0.0.
I said earlier that NaN != Nan
, but if you compare with equals()
,
the result is true:
jshell> Float f1 = Float.valueOf(Float.NaN) f1 ==> NaN jshell> Float f2 = Float.valueOf(Float.NaN) f2 ==> NaN jshell> f1 == f2 # Comparing object identities $4 ==> false jshell> f1.equals(f1) # bitwise comparison of values $5 ==> true
If this sounds weird, remember that the complexity comes partly from the nature of doing real number computations in the less-precise floating-point hardware, and partly from the details of the IEEE Standard 754, which specifies the floating-point functionality that Java tries to adhere to, so that underlying floating-point processor hardware can be used even when Java programs are being interpreted.
To actually compare floating-point numbers for equality, it is generally desirable to compare them within some tiny range of allowable differences; this range is often regarded as a tolerance or as epsilon. Example 5-2 shows an equals()
method you can use to do this comparison, as well as comparisons on values of NaN
. When run, it prints that the first two numbers are equal within epsilon:
$ java numbers.FloatCmp True within epsilon 1.0E-7 $
public
class
FloatCmp
{
final
static
double
EPSILON
=
0.0000001
;
public
static
void
main
(
String
[]
argv
)
{
double
da
=
3
*
.
3333333333
;
double
db
=
0.99999992857
;
// Compare two numbers that are expected to be close.
if
(
da
==
db
)
{
System
.
out
.
println
(
"Java considers "
+
da
+
"=="
+
db
);
// else compare with our own equals overload
}
else
if
(
equals
(
da
,
db
,
0.0000001
))
{
System
.
out
.
println
(
"Equal within epsilon "
+
EPSILON
);
}
else
{
System
.
out
.
println
(
da
+
" != "
+
db
);
}
System
.
out
.
println
(
"NaN prints as "
+
Double
.
NaN
);
// Show that comparing two NaNs is not a good idea:
double
nan1
=
Double
.
NaN
;
double
nan2
=
Double
.
NaN
;
if
(
nan1
==
nan2
)
System
.
out
.
println
(
"Comparing two NaNs incorrectly returns true."
);
else
System
.
out
.
println
(
"Comparing two NaNs correctly reports false."
);
if
(
Double
.
valueOf
(
nan1
).
equals
(
Double
.
valueOf
(
nan2
)))
System
.
out
.
println
(
"Double(NaN).equals(NaN) correctly returns true."
);
else
System
.
out
.
println
(
"Double(NaN).equals(NaN) incorrectly returns false."
);
}
/** Compare two doubles within a given epsilon */
public
static
boolean
equals
(
double
a
,
double
b
,
double
eps
)
{
if
(
a
==
b
)
return
true
;
// If the difference is less than epsilon, treat as equal.
return
Math
.
abs
(
a
-
b
)
<
eps
;
}
/** Compare two doubles, using default epsilon */
public
static
boolean
equals
(
double
a
,
double
b
)
{
return
equals
(
a
,
b
,
EPSILON
);
}
}
Note that neither of the System.err
messages about “incorrect returns” prints. The point of this example with NaN
s is that you should always make sure values are not NaN
before entrusting them to Double.equals()
.
If you simply cast a floating value to an integer value, Java truncates the value. A value like 3.999999 cast to an int
or long
becomes 3, not 4. To round floating-point numbers properly, use Math.round()
. It has two overloads: if you give it a double
, you get a long
result; if you give it a float
, you get an int
.
What if you don’t like the rounding rules used by round
? If, for some bizarre reason, you wanted to round numbers greater than 0.54 instead of the normal 0.5, you could write your own version of round()
:
public
class
Round
{
/** We round a number up if its fraction exceeds this threshold. */
public
static
final
double
THRESHOLD
=
0.54
;
/*
* Round floating values to integers.
* @return the closest int to the argument.
* @param d A non-negative values to be rounded.
*/
public
static
int
round
(
double
d
)
{
return
(
int
)
Math
.
floor
(
d
+
1.0
-
THRESHOLD
);
}
public
static
void
main
(
String
[]
argv
)
{
for
(
double
d
=
0.1
;
d
<=
1.0
;
d
+=
0.05
)
{
System
.
out
.
println
(
"My way: "
+
d
+
"-> "
+
round
(
d
));
System
.
out
.
println
(
"Math way:"
+
d
+
"-> "
+
Math
.
round
(
d
));
}
}
}
If, on the other hand, you simply want to display a number with less precision than it normally
gets, you probably want to use a DecimalFormat
object or a Formatter
object, which we
look at in Recipe 5.5.
You need to format numbers.
Use a NumberFormat
subclass.
Java did not originally provide C-style printf/
scanf functions because they tend to mix together formatting and input/output in a very inflexible way. Programs using printf/
scanf can be hard to internationalize, for example.
Of course, “by popular demand,” Java did eventually introduce printf()
, which along with String.format()
is now standard in Java; see Recipe 10.4.
Java has an entire package, java.text
, full of formatting routines as general and flexible as anything you might imagine. As with printf, it has an involved formatting language, described in the javadoc page. Consider the presentation of long numbers. In North America, the number one thousand twenty-four and a quarter is written 1,024.25, in most of Europe it is 1 024,25, and in some other part of the world it might be written 1.024,25. Not to mention how currencies and percentages are formatted! Trying to keep track of this yourself would drive the average small software shop around the bend rather quickly.
Fortunately, the java.text
package includes a Locale
class, and, furthermore, the Java runtime automatically sets a default Locale
object based on the user’s environment; (on the Macintosh and Windows, the user’s preferences, and on Unix, the user’s environment variables). (To provide a nondefault locale in code, see Recipe 3.12.) To provide formatters customized for numbers, currencies, and percentages, the NumberFormat
class has static factory methods that normally return a DecimalFormat
with the correct pattern already instantiated. A DecimalFormat
object appropriate to the user’s locale can be obtained from the factory method NumberFormat.getInstance()
and manipulated using set
methods. Surprisingly, the method setMinimumIntegerDigits()
turns out to be the easy way to generate a number format with leading zeros. Here is an example:
public
class
NumFormat2
{
/** A number to format */
public
static
final
double
data
[]
=
{
0
,
1
,
22
d
/
7
,
100.2345678
};
/** The main (and only) method in this class. */
public
static
void
main
(
String
[]
av
)
{
// Get a format instance
NumberFormat
form
=
NumberFormat
.
getInstance
();
// Set it to look like 999.99[99]
form
.
setMinimumIntegerDigits
(
3
);
form
.
setMinimumFractionDigits
(
2
);
form
.
setMaximumFractionDigits
(
4
);
// Now print using it.
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
System
.
out
.
println
(
data
[
i
]
+
" formats as "
+
form
.
format
(
data
[
i
]));
}
}
This prints the contents of the array using the NumberFormat
instance form
:
$ java numbers.NumFormat2 0.0 formats as 000.00 1.0 formats as 001.00 3.142857142857143 formats as 003.1429 100.2345678 formats as 100.2346 $
You can also construct a DecimalFormat
with a particular pattern or change the pattern dynamically using applyPattern()
. Some of the more common pattern characters are shown in Table 5-2.
Character | Meaning |
---|---|
|
Numeric digit (leading zeros suppressed) |
|
Numeric digit (leading zeros provided) |
. |
Locale-specific decimal separator (decimal point) |
, |
Locale-specific grouping separator (comma in English) |
|
Locale-specific negative indicator (minus sign) |
|
Shows the value as a percentage |
|
Separates two formats: the first for positive and the second for negative values |
' |
Escapes one of the above characters so it appears |
Anything else |
Appears as itself |
The NumFormatDemo
program uses one DecimalFormat
to print a number with only two decimal places and a second to format the number according to the default locale:
/** A number to format */
public
static
final
double
intlNumber
=
1024.25
;
/** Another number to format */
public
static
final
double
ourNumber
=
100.2345678
;
NumberFormat
defForm
=
NumberFormat
.
getInstance
();
NumberFormat
ourForm
=
new
DecimalFormat
(
"##0.##"
);
// toPattern() will reveal the combination of #0., etc
// that this particular Locale uses to format with!
System
.
out
.
println
(
"defForm's pattern is "
+
((
DecimalFormat
)
defForm
).
toPattern
());
System
.
out
.
println
(
intlNumber
+
" formats as "
+
defForm
.
format
(
intlNumber
));
System
.
out
.
println
(
ourNumber
+
" formats as "
+
ourForm
.
format
(
ourNumber
));
System
.
out
.
println
(
ourNumber
+
" formats as "
+
defForm
.
format
(
ourNumber
)
+
" using the default format"
);
This program prints the given pattern and then formats the same number using several formats:
$ java numbers.NumFormatDemo defForm's pattern is #,##0.### 1024.25 formats as 1,024.25 100.2345678 formats as 100.23 100.2345678 formats as 100.235 using the default format $
To print a number in what Linux/Unix calls “human readable format” (many display commands accept a -h argument for this format), use the Java 12 CompactNumberFormat, as shown in Example 5-3.
public
class
CompactFormatDemo
{
static
final
Number
[]
nums
=
{
0
,
1
,
1.25
,
1234
,
12345
,
123456.78
,
123456789012L
};
static
final
String
[]
strs
=
{
"1"
,
"1.25"
,
"1234"
,
"12.345K"
,
"1234556.78"
,
"123456789012L"
};
public
static
void
main
(
String
[]
args
)
throws
ParseException
{
NumberFormat
cnf
=
NumberFormat
.
getCompactNumberInstance
();
System
.
out
.
println
(
"Formatting:"
);
for
(
Number
n
:
nums
)
{
cnf
.
setParseIntegerOnly
(
false
);
cnf
.
setMinimumFractionDigits
(
2
);
System
.
out
.
println
(
n
+
": "
+
cnf
.
format
(
n
));
}
System
.
out
.
println
(
"Parsing:"
);
for
(
String
s
:
strs
)
{
System
.
out
.
println
(
s
+
": "
+
cnf
.
parse
(
s
));
}
}
}
To work with Roman Numerals, use my RomanNumberFormat
class, as in this demo:
RomanNumberFormat
nf
=
new
RomanNumberFormat
();
int
year
=
LocalDate
.
now
().
getYear
();
System
.
out
.
println
(
year
+
" -> "
+
nf
.
format
(
year
));
Running RomanNumberSimple
in 2020 produces this output:
2020->MMXX
The source of the RomanNumberFormat class is in src/main/java/numbers/RomanNumberFormat.java.
Several of the public methods are required because I wanted it to be a subclass of Format
, which is abstract.
This accounts for some of the complexity, like having three different format methods.
Note that the RomanNumberFormat.parseObject( )
method is also required, but the code doesn’t
implement parsing in this version. This is left as an exercise for the reader.
Java I/O (O’Reilly) includes an entire chapter on NumberFormat
and develops the subclass ExponentialNumberFormat
.
You want to display an integer as a series of bits—for example, when interacting with certain hardware devices—or in some alternative number base (binary is base 2, octal is base 8, decimal is 10, hexadecimal is 16). You want to convert a binary number or a hexadecimal value into an integer.
The class java.lang.Integer
provides the solutions. Most of the time you can use Integer.parseInt(String input, int radix)
to convert from any type of number to an Integer
, and Integer.toString(int input, int radix)
to go the other way. Example 5-4 shows some examples of using the Integer
class.
String
input
=
"101010"
;
for
(
int
radix
:
new
int
[]
{
2
,
8
,
10
,
16
,
36
})
{
System
.
out
.
(
input
+
" in base "
+
radix
+
" is "
+
Integer
.
valueOf
(
input
,
radix
)
+
"; "
);
int
i
=
42
;
System
.
out
.
println
(
i
+
" formatted in base "
+
radix
+
" is "
+
Integer
.
toString
(
i
,
radix
));
}
This program prints the binary string as an integer in various bases, and the integer 42 in those same number bases:
$ java numbers.IntegerBinOctHexEtc 101010 in base 2 is 42; 42 formatted in base 2 is 101010 101010 in base 8 is 33288; 42 formatted in base 8 is 52 101010 in base 10 is 101010; 42 formatted in base 10 is 42 101010 in base 16 is 1052688; 42 formatted in base 16 is 2a 101010 in base 36 is 60512868; 42 formatted in base 36 is 16 $
There are also specialized versions of toString(int)
that don’t require you to specify
the radix; for example, toBinaryString()
to convert an integer to binary,
toHexString()
for hexadecimal, toOctalString()
, and so on. The javadoc page for the Integer
class is
your friend here.
The String
class itself includes a series of static methods—valueOf(int)
, valueOf(double)
, and so on—that also provide default formatting. That is, they return the given numeric value formatted as a string.
You need to work on a range of integers.
For a contiguous set, use IntStream::range
and rangeClosed
, or the older for
loop.
For discontinuous ranges of numbers, use a java.util.BitSet
.
To process a contiguous set of integers, Java provides both range()
/ rangeClosed()
methods in the IntStream
and LongStream
classes.
These take a starting and ending number; range()
excludes the ending number while
rangeClosed()
“closes on” or includes the ending number.
You can also iterate over a range of numbers using the traditional for
loop.
Loop control for the for
loop is in three parts: initialize, test, and
change. If the test part is initially false, the loop will never be
executed, not even once. You can iterate over the elements of an array or
collection (see Chapter 7) using a “foreach” loop.
The program in Example 5-5 demonstrates these techniques:
public
class
NumSeries
{
public
static
void
main
(
String
[]
args
)
{
// For ordinal list of numbers n to m, use rangeClosed(start, endInclusive)
IntStream
.
rangeClosed
(
1
,
12
).
forEach
(
i
->
System
.
out
.
println
(
"Month # "
+
i
));
// Or, use a for loop starting at 1.
for
(
int
i
=
1
;
i
<=
months
.
length
;
i
++)
System
.
out
.
println
(
"Month # "
+
i
);
// Or a foreach loop
for
(
String
month
:
months
)
{
System
.
out
.
println
(
month
);
}
// When you want a set of array indices, use range(start, endExclusive)
IntStream
.
range
(
0
,
months
.
length
).
forEach
(
i
->
System
.
out
.
println
(
"Month "
+
months
[
i
]));
// Or, use a for loop starting at 0.
for
(
int
i
=
0
;
i
<
months
.
length
;
i
++)
System
.
out
.
println
(
"Month "
+
months
[
i
]);
// For e.g., counting by 3 from 11 to 27, use a for loop
for
(
int
i
=
11
;
i
<=
27
;
i
+=
3
)
{
System
.
out
.
println
(
"i = "
+
i
);
}
// A discontiguous set of integers, using a BitSet
// Create a BitSet and turn on a couple of bits.
BitSet
b
=
new
BitSet
();
b
.
set
(
0
);
// January
b
.
set
(
3
);
// April
b
.
set
(
8
);
// September
// Presumably this would be somewhere else in the code.
for
(
int
i
=
0
;
i
<
months
.
length
;
i
++)
{
if
(
b
.
get
(
i
))
System
.
out
.
println
(
"Month "
+
months
[
i
]);
}
// Same example but shorter:
// a discontiguous set of integers, using an array
int
[]
numbers
=
{
0
,
3
,
8
};
// Presumably somewhere else in the code... Also a foreach loop
for
(
int
n
:
numbers
)
{
System
.
out
.
println
(
"Month: "
+
months
[
n
]);
}
}
// tag::inner[]
/** Names of months. See Dates/Times chapter for a better way to get these */
protected
static
String
months
[]
=
{
"January"
,
"February"
,
"March"
,
"April"
,
"May"
,
"June"
,
"July"
,
"August"
,
"September"
,
"October"
,
"November"
,
"December"
};
// end::inner[]
}
You’re printing something like "We used " + n + " items"
, but in English,
“We used 1 items” is ungrammatical. You want “We used 1 item.”
Use a ChoiceFormat
or a conditional statement.
Use Java’s ternary operator (cond ? trueval
: falseval
) in a string concatenation. Both zero and plurals get an “s” appended to the noun in English (“no books, one book, two books”), so we test for n==1
:
public
class
FormatPlurals
{
public
static
void
main
(
String
[]
argv
)
{
report
(
0
);
report
(
1
);
report
(
2
);
}
/** report -- using conditional operator */
public
static
void
report
(
int
n
)
{
System
.
out
.
println
(
"We used "
+
n
+
" item"
+
(
n
==
1
?
""
:
"s"
));
}
}
Does it work?
$ java numbers.FormatPlurals We used 0 items We used 1 item We used 2 items $
The final println
statement is effectively equivalent to:
if
(
n
==
1
)
System
.
out
.
println
(
"We used "
+
n
+
" item"
);
else
System
.
out
.
println
(
"We used "
+
n
+
" items"
);
This is a lot longer, so the ternary conditional operator is worth learning.
The ChoiceFormat
is ideal for this. It is actually capable of much more, but here I’ll show only this simplest use. I specify the values 0, 1, and 2 (or more), and the string values to print corresponding to each number. The numbers are then formatted according to the range they fall into:
public
class
FormatPluralsChoice
extends
FormatPlurals
{
// ChoiceFormat to just give pluralized word
static
double
[]
limits
=
{
0
,
1
,
2
};
static
String
[]
formats
=
{
"reviews"
,
"review"
,
"reviews"
};
static
ChoiceFormat
pluralizedFormat
=
new
ChoiceFormat
(
limits
,
formats
);
// ChoiceFormat to give English text version, quantified
static
ChoiceFormat
quantizedFormat
=
new
ChoiceFormat
(
"0#no reviews|1#one review|1<many reviews"
);
// Test data
static
int
[]
data
=
{
-
1
,
0
,
1
,
2
,
3
};
public
static
void
main
(
String
[]
argv
)
{
System
.
out
.
println
(
"Pluralized Format"
);
for
(
int
i
:
data
)
{
System
.
out
.
println
(
"Found "
+
i
+
" "
+
pluralizedFormat
.
format
(
i
));
}
System
.
out
.
println
(
"Quantized Format"
);
for
(
int
i
:
data
)
{
System
.
out
.
println
(
"Found "
+
quantizedFormat
.
format
(
i
));
}
}
}
This generates the same output as the basic version. It is slightly longer, but more general, and lends itself better to internationalization.
In addition to ChoiceFormat
, the same result can be achieved with a MessageFormat
;
there’s an example in the online source in file i18n/MessageFormatDemo.java.
You need to generate random numbers in a hurry.
Use java.lang.Math.random()
to generate random numbers. There is no claim that the random values it returns are very good random numbers, however.
Like most software-only implementations, these are pseudo-random number generators (PRNGs),
meaning, the numbers are not totally random, but devise from an algorithm.
That said, they are adequate for casual use.
This code exercises the random()
method:
// numbers/Random1.java
// java.lang.Math.random( ) is static, don't need any constructor calls
System
.
out
.
println
(
"A random from java.lang.Math is "
+
Math
.
random
(
));
Note that this method only generates double values. If you need integers, construct a
java.util.Random
object and call its nextInt()
method; if you pass it an integer value,
this will become the upper bound. Here I generate integers from 1 to 10:
public
class
RandomInt
{
public
static
void
main
(
String
[]
a
)
{
Random
r
=
new
Random
();
for
(
int
i
=
0
;
i
<
1000
;
i
++)
// nextInt(10) goes from 0-9; add 1 for 1-10;
System
.
out
.
println
(
1
+
r
.
nextInt
(
10
));
}
}
To see if my RandomInt
demo was really working well, I used the Unix tools sort and uniq, which together give a count of how many times each value was chosen. For 1,000 integers, each
of 10 values should be chosen about 100 times. I ran it twice to get a better idea
of the distribution.
$ java numbers.RandomInt | sort | uniq -c | sort -k 2 -n 96 1 107 2 102 3 122 4 99 5 105 6 97 7 96 8 79 9 97 10 $ java -cp build numbers.RandomInt | sort | uniq -c | sort -k 2 -n 86 1 88 2 110 3 97 4 99 5 109 6 82 7 116 8 99 9 114 10 $
The next step is to run these through a statistical program to see how really random they are; we’ll return to this in a minute.
In general, to generate random numbers, you need to construct a java.util.Random
object (not just any old random object) and call its
next*()
methods. These methods include nextBoolean()
, nextBytes()
(which fills the given array of bytes with random values), nextDouble()
, nextFloat()
, nextInt()
, and nextLong()
. Don’t be confused by the capitalization of Float
, Double
, etc. They return the primitive types boolean
, float
, double
, etc., not the capitalized wrapper objects. Clear enough? Maybe an example will help:
// java.util.Random methods are non-static, so need to construct
Random
r
=
new
Random
();
for
(
int
i
=
0
;
i
<
10
;
i
++)
System
.
out
.
println
(
"A double from java.util.Random is "
+
r
.
nextDouble
());
for
(
int
i
=
0
;
i
<
10
;
i
++)
System
.
out
.
println
(
"An integer from java.util.Random is "
+
r
.
nextInt
());
A fixed value (“starting seed”) can be provided to generate repeatable values, as for testing.
You can also use the java.util.Random nextGaussian()
method, as shown next. The nextDouble()
methods try to give a “flat” distribution between 0 and 1.0, in which each value has an equal chance of being selected. A Gaussian or normal distribution is a bell-curve of values from negative infinity to positive infinity, with the majority of the values around zero (0.0).
// numbers/Random3.java
Random
r
=
new
Random
();
for
(
int
i
=
0
;
i
<
10
;
i
++)
System
.
out
.
println
(
"A gaussian random double is "
+
r
.
nextGaussian
());
To illustrate the different distributions, I generated 10,000 numbers using
nextRandom()
first and then using nextGaussian()
. The code for this is in Random4.java
(not shown here) and is a combination of the previous programs with code to print the
results into files. I then plotted histograms using the R statistics package (see
Chapter 11 and http://www.r-project.org.
The R script used to generate the graph, randomnesshistograms.r, is in
javasrc under src/main/resources). The results are shown in
Figure 5-1.
Looks like both PRNG’s do their job!
The javadoc documentation for java.util.Random
, and the warning in Recipe 5.0 about pseudorandomness versus real randomness.
For cryptographic use, see class java.security.SecureRandom
, which provides cryptographically strong pseudorandom number generators (PRNG).
You need to multiply a pair of two-dimensional arrays, as is common in mathematical and engineering applications.
Use the following code as a model.
It is straightforward to multiply an array of a numeric type. In real life you would probably use a full-blown package such as the Efficient Java Matrix Library (EJML) or DeepLearning4Java’s ND4J package. However a simple implementation can serve to show the concepts involved; the code in Example 5-6 implements matrix multiplication.
public
class
Matrix
{
/* Matrix-multiply two arrays together.
* The arrays MUST be rectangular.
* @author Adapted from Tom Christiansen & Nathan Torkington's
* implementation in their Perl Cookbook.
*/
public
static
int
[][]
multiply
(
int
[][]
m1
,
int
[][]
m2
)
{
int
m1rows
=
m1
.
length
;
int
m1cols
=
m1
[
0
].
length
;
int
m2rows
=
m2
.
length
;
int
m2cols
=
m2
[
0
].
length
;
if
(
m1cols
!=
m2rows
)
throw
new
IllegalArgumentException
(
"matrices don't match: "
+
m1cols
+
" != "
+
m2rows
);
int
[][]
result
=
new
int
[
m1rows
][
m2cols
];
// multiply
for
(
int
i
=
0
;
i
<
m1rows
;
i
++)
{
for
(
int
j
=
0
;
j
<
m2cols
;
j
++)
{
for
(
int
k
=
0
;
k
<
m1cols
;
k
++)
{
result
[
i
][
j
]
+=
m1
[
i
][
k
]
*
m2
[
k
][
j
];
}
}
}
return
result
;
}
/** Matrix print.
*/
public
static
void
mprint
(
int
[][]
a
)
{
int
rows
=
a
.
length
;
int
cols
=
a
[
0
].
length
;
System
.
out
.
println
(
"array["
+
rows
+
"]["
+
cols
+
"] = {"
);
for
(
int
i
=
0
;
i
<
rows
;
i
++)
{
System
.
out
.
(
"{"
);
for
(
int
j
=
0
;
j
<
cols
;
j
++)
System
.
out
.
(
" "
+
a
[
i
][
j
]
+
","
);
System
.
out
.
println
(
"},"
);
}
System
.
out
.
println
(
"};"
);
}
}
Here is a program that uses the Matrix
class to multiply two arrays of int
s:
int
x
[][]
=
{
{
3
,
2
,
3
},
{
5
,
9
,
8
},
};
int
y
[][]
=
{
{
4
,
7
},
{
9
,
3
},
{
8
,
1
},
};
int
z
[][]
=
Matrix
.
multiply
(
x
,
y
);
Matrix
.
mprint
(
x
);
Matrix
.
mprint
(
y
);
Matrix
.
mprint
(
z
);
Consult a book on numerical methods for more things to do with matrices; one of our reviewers recommends Numerical Recipes in Fortran by Teukolsky, Flannery, et al., available as a PDF and for online viewing. The example recipes in that book have been translated into Java but are only available in this form to licensed purchasers of the software.
Commercial software packages can do some of these calculations for you; for one example, see the numeric libraries available from Rogue Wave Software.
You need to manipulate complex numbers, as is common in mathematical, scientific, or engineering applications.
Java does not provide any explicit support for dealing with complex numbers. You could keep track of the real and imaginary parts and do the computations yourself, but that is not a very well-structured solution.
A better solution, of course, is to use a class that implements complex numbers.
I once wrote just such a class, but now recommend using the
Apache Commons Math library for this.
The build coordinates for this are org.apache.commons:commons-math3:3.6.1
(or later).
First, an example of using Apache’s library:
public
class
ComplexDemoACM
{
public
static
void
main
(
String
[]
args
)
{
Complex
c
=
new
Complex
(
3
,
5
);
Complex
d
=
new
Complex
(
2
,
-
2
);
System
.
out
.
println
(
c
);
System
.
out
.
println
(
c
+
".getReal() = "
+
c
.
getReal
());
System
.
out
.
println
(
c
+
" + "
+
d
+
" = "
+
c
.
add
(
d
));
System
.
out
.
println
(
c
+
" + "
+
d
+
" = "
+
c
.
add
(
d
));
System
.
out
.
println
(
c
+
" * "
+
d
+
" = "
+
c
.
multiply
(
d
));
System
.
out
.
println
(
c
.
divide
(
d
));
}
}
Running this demo program produces the following output:
(3.0, 5.0) (3.0, 5.0).getReal() = 3.0 (3.0, 5.0) + (2.0, -2.0) = (5.0, 3.0) (3.0, 5.0) + (2.0, -2.0) = (5.0, 3.0) (3.0, 5.0) * (2.0, -2.0) = (16.0, 4.0) (-0.5, 2.0)
Example 5-7 is the source for my version of the Complex
class and shouldn’t require much explanation. The Apache one is admittedly more sophisticated, but I leave mine here just
to de-mystify the basic operation of complex numbers.
To keep the API general, I provide—for each of add, subtract, and multiply—both a static method that works on two complex objects and a nonstatic method that applies the operation to the given object and one other object.
public
class
Complex
{
/** The real part */
private
double
r
;
/** The imaginary part */
private
double
i
;
/** Construct a Complex */
Complex
(
double
rr
,
double
ii
)
{
r
=
rr
;
i
=
ii
;
}
/** Display the current Complex as a String, for use in
* println() and elsewhere.
*/
public
String
toString
()
{
StringBuilder
sb
=
new
StringBuilder
().
append
(
r
);
if
(
i
>
0
)
sb
.
append
(
'+'
);
// else append(i) appends - sign
return
sb
.
append
(
i
).
append
(
'i'
).
toString
();
}
/** Return just the Real part */
public
double
getReal
()
{
return
r
;
}
/** Return just the Real part */
public
double
getImaginary
()
{
return
i
;
}
/** Return the magnitude of a complex number */
public
double
magnitude
()
{
return
Math
.
sqrt
(
r
*
r
+
i
*
i
);
}
/** Add another Complex to this one
*/
public
Complex
add
(
Complex
other
)
{
return
add
(
this
,
other
);
}
/** Add two Complexes
*/
public
static
Complex
add
(
Complex
c1
,
Complex
c2
)
{
return
new
Complex
(
c1
.
r
+
c2
.
r
,
c1
.
i
+
c2
.
i
);
}
/** Subtract another Complex from this one
*/
public
Complex
subtract
(
Complex
other
)
{
return
subtract
(
this
,
other
);
}
/** Subtract two Complexes
*/
public
static
Complex
subtract
(
Complex
c1
,
Complex
c2
)
{
return
new
Complex
(
c1
.
r
-
c2
.
r
,
c1
.
i
-
c2
.
i
);
}
/** Multiply this Complex times another one
*/
public
Complex
multiply
(
Complex
other
)
{
return
multiply
(
this
,
other
);
}
/** Multiply two Complexes
*/
public
static
Complex
multiply
(
Complex
c1
,
Complex
c2
)
{
return
new
Complex
(
c1
.
r
*
c2
.
r
-
c1
.
i
*
c2
.
i
,
c1
.
r
*
c2
.
i
+
c1
.
i
*
c2
.
r
);
}
/** Divide c1 by c2.
* @author Gisbert Selke.
*/
public
static
Complex
divide
(
Complex
c1
,
Complex
c2
)
{
return
new
Complex
(
(
c1
.
r
*
c2
.
r
+
c1
.
i
*
c2
.
i
)/(
c2
.
r
*
c2
.
r
+
c2
.
i
*
c2
.
i
),
(
c1
.
i
*
c2
.
r
-
c1
.
r
*
c2
.
i
)/(
c2
.
r
*
c2
.
r
+
c2
.
i
*
c2
.
i
));
}
/* Compare this Complex number with another
*/
public
boolean
equals
(
Object
o
)
{
if
(
o
.
getClass
()
!=
Complex
.
class
)
{
throw
new
IllegalArgumentException
(
"Complex.equals argument must be a Complex"
);
}
Complex
other
=
(
Complex
)
o
;
return
r
==
other
.
r
&&
i
==
other
.
i
;
}
/* Generate a hashCode; not sure how well distributed these are.
*/
public
int
hashCode
()
{
return
(
int
)(
r
)
|
(
int
)
i
;
}
}
You need to handle integer numbers larger than Long.MAX_VALUE
or floating-point values larger than Double.MAX_VALUE
.
Use the BigInteger
or BigDecimal
values in package java.math
, as shown in Example 5-8.
System
.
out
.
println
(
"Here's Long.MAX_VALUE: "
+
Long
.
MAX_VALUE
);
BigInteger
bInt
=
new
BigInteger
(
"3419229223372036854775807"
);
System
.
out
.
println
(
"Here's a bigger number: "
+
bInt
);
System
.
out
.
println
(
"Here it is as a double: "
+
bInt
.
doubleValue
());
Note that the constructor takes the number as a string. Obviously you couldn’t just type the numeric digits because, by definition, these classes are designed to represent numbers larger than will fit in a Java long
.
Both BigInteger
and BigDecimal
objects are immutable; that is, once constructed, they always represent a given number. That said, a number of methods return new objects that are mutations of the original, such as negate()
, which returns the negative of the given BigInteger
or BigDecimal
. There are also methods corresponding to most of the Java language built-in operators defined on the base types int
/long
and float
/double
. The division method makes you specify the rounding method; consult a book on numerical analysis for details. Example 5-9 is a simple stack-based calculator using BigDecimal
as its numeric data type.
public
class
BigNumCalc
{
/** an array of Objects, simulating user input */
public
static
Object
[]
testInput
=
{
new
BigDecimal
(
"3419229223372036854775807.23343"
),
new
BigDecimal
(
"2.0"
),
"*"
,
};
public
static
void
main
(
String
[]
args
)
{
BigNumCalc
calc
=
new
BigNumCalc
();
System
.
out
.
println
(
calc
.
calculate
(
testInput
));
}
/**
* Stack of numbers being used in the calculator.
*/
Stack
<
BigDecimal
>
stack
=
new
Stack
<>();
/**
* Calculate a set of operands; the input is an Object array containing
* either BigDecimal objects (which may be pushed onto the Stack) and
* operators (which are operated on immediately).
* @param input
* @return
*/
public
BigDecimal
calculate
(
Object
[]
input
)
{
BigDecimal
tmp
;
for
(
int
i
=
0
;
i
<
input
.
length
;
i
++)
{
Object
o
=
input
[
i
];
if
(
o
instanceof
BigDecimal
)
{
stack
.
push
((
BigDecimal
)
o
);
}
else
if
(
o
instanceof
String
)
{
switch
(((
String
)
o
).
charAt
(
0
))
{
// + and * are commutative, order doesn't matter
case
'+'
:
stack
.
push
((
stack
.
pop
()).
add
(
stack
.
pop
()));
break
;
case
'*'
:
stack
.
push
((
stack
.
pop
()).
multiply
(
stack
.
pop
()));
break
;
// - and /, order *does* matter
case
'-'
:
tmp
=
(
BigDecimal
)
stack
.
pop
();
stack
.
push
((
stack
.
pop
()).
subtract
(
tmp
));
break
;
case
'/'
:
tmp
=
stack
.
pop
();
stack
.
push
((
stack
.
pop
()).
divide
(
tmp
,
BigDecimal
.
ROUND_HALF_UP
));
break
;
default
:
throw
new
IllegalStateException
(
"Unknown OPERATOR popped"
);
}
}
else
{
throw
new
IllegalArgumentException
(
"Syntax error in input"
);
}
}
return
stack
.
pop
();
}
}
Running this produces the expected (very large) value:
> javac -d . numbers/BigNumCalc.java > java numbers.BigNumCalc 6838458446744073709551614.466860 >
The current version has its inputs hardcoded, as does the JUnit
test program, but in real life you can use regular expressions to extract words or operators from an input stream (as in Recipe 4.5), or you can use the StreamTokenizer
approach of the simple calculator (see Recipe 10.5). The stack of numbers is maintained using a java.util.Stack
(see Recipe 7.16).
BigInteger
is mainly useful in cryptographic and security applications. Its method isProbablyPrime()
can create prime pairs for public key cryptography. BigDecimal
might also be useful in computing the size of the universe.
The program shown in Example 5-10 prints a table of Fahrenheit temperatures (still used in daily weather reporting in the United States) and the corresponding Celsius temperatures (used in science everywhere, and in daily life in most of the world).
public
class
TempConverter
{
public
static
void
main
(
String
[]
args
)
{
TempConverter
t
=
new
TempConverter
();
t
.
start
();
t
.
data
();
t
.
end
();
}
protected
void
start
()
{
}
protected
void
data
()
{
for
(
int
i
=-
40
;
i
<=
120
;
i
+=
10
)
{
double
c
=
fToC
(
i
);
(
i
,
c
);
}
}
public
static
double
cToF
(
double
deg
)
{
return
(
deg
*
9
/
5
)
+
32
;
}
public
static
double
fToC
(
double
deg
)
{
return
(
deg
-
32
)
*
(
5
d
/
9
);
}
protected
void
(
double
f
,
double
c
)
{
System
.
out
.
println
(
f
+
" "
+
c
);
}
protected
void
end
()
{
}
}
This works, but these numbers print with about 15 digits of (useless)
decimal fractions! The second version of this program subclasses
the first and uses printf
(see Recipe 10.4) to control
the formatting of the converted temperatures (see Example 5-11).
It will now look right, assuming you’re printing in a monospaced font.
public
class
TempConverter2
extends
TempConverter
{
public
static
void
main
(
String
[]
args
)
{
TempConverter
t
=
new
TempConverter2
();
t
.
start
();
t
.
data
();
t
.
end
();
}
@Override
protected
void
(
double
f
,
double
c
)
{
System
.
out
.
printf
(
"%6.2f %6.2f%n"
,
f
,
c
);
}
@Override
protected
void
start
()
{
System
.
out
.
println
(
"Fahr Centigrade"
);
}
@Override
protected
void
end
()
{
System
.
out
.
println
(
"-------------------"
);
}
}
C:javasrc umbers>java numbers.TempConverter2 Fahr Centigrade -40.00 -40.00 -30.00 -34.44 -20.00 -28.89 -10.00 -23.33 0.00 -17.78 10.00 -12.22 20.00 -6.67 30.00 -1.11 40.00 4.44 50.00 10.00 60.00 15.56 70.00 21.11 80.00 26.67 90.00 32.22 100.00 37.78 110.00 43.33 120.00 48.89
My wife, Betty, recently reminded me of a theorem that I must have studied in high school but whose name I have long since forgotten: that any positive integer number can be used to generate a palindrome by adding to it the number comprised of its digits in reverse order. Palindromes are sequences that read the same in either direction, such as the name “Anna” or the phrase “Madam, I’m Adam” (ignoring spaces and punctuation). We normally think of palindromes as composed of text, but the concept can be applied to numbers: 13531 is a palindrome. Start with the number 72, for example, and add to it the number 27. The results of this addition is 99, which is a (short) palindrome. Starting with 142, add 241, and you get 383. Some numbers take more than one try to generate a palindrome. 1951 + 1591 yields 3542, which is not palindromic. The second round, however, 3542 + 2453, yields 5995, which is. The number 17,892, which my son Benjamin picked out of the air, requires 12 rounds to generate a palindrome, but it does terminate:
C:javasrc umbers>java numbers.Palindrome 72 142 1951 17892 Trying 72 72->99 Trying 142 142->383 Trying 1951 Trying 3542 1951->5995 Trying 17892 Trying 47763 Trying 84537 Trying 158085 Trying 738936 Trying 1378773 Trying 5157504 Trying 9215019 Trying 18320148 Trying 102422529 Trying 1027646730 Trying 1404113931 17892->2797227972 C:javasrc umbers>
If this sounds to you like a natural candidate for recursion, you are correct. Recursion involves dividing a problem into simple and identical steps, which can be implemented by a function that calls itself and provides a way of termination. Our basic approach, as shown in method findPalindrome
, is:
long findPalindrome(long num) { if (isPalindrome(num)) return num; return findPalindrome(num + reverseNumber(num)); }
That is, if the starting number is already a palindromic number, return it; otherwise, add it to its reverse, and try again. The version of the code shown here handles simple cases directly (single digits are always palindromic, for example). We won’t think about negative numbers because these have a character at the front that loses its meaning if placed at the end, and hence are not strictly palindromic. Further, palindromic forms of certain numbers are too long to fit in Java’s 64-bit long
integer. These cause underflow, which is trapped. As a result, an error message like “too big” is reported.3 Having said all that, Example 5-12 shows the code.
public
class
Palindrome
{
public
static
boolean
verbose
=
true
;
public
static
void
main
(
String
[]
argv
)
{
for
(
String
num
:
argv
)
{
try
{
long
l
=
Long
.
parseLong
(
num
);
if
(
l
<
0
)
{
System
.
err
.
println
(
num
+
" -> TOO SMALL"
);
continue
;
}
System
.
out
.
println
(
num
+
"->"
+
findPalindrome
(
l
));
}
catch
(
NumberFormatException
e
)
{
System
.
err
.
println
(
num
+
"-> INVALID"
);
}
catch
(
IllegalStateException
e
)
{
System
.
err
.
println
(
num
+
"-> "
+
e
);
}
}
}
/** find a palindromic number given a starting point, by
* recursing until we get a number that is palindromic.
*/
static
long
findPalindrome
(
long
num
)
{
if
(
num
<
0
)
throw
new
IllegalStateException
(
"negative"
);
if
(
isPalindrome
(
num
))
return
num
;
if
(
verbose
)
System
.
out
.
println
(
"Trying "
+
num
);
return
findPalindrome
(
num
+
reverseNumber
(
num
));
}
/** The number of digits in Long.MAX_VALUE */
protected
static
final
int
MAX_DIGITS
=
19
;
// digits array is shared by isPalindrome and reverseNumber,
// which cannot both be running at the same time.
/* Statically allocated array to avoid new-ing each time. */
static
long
[]
digits
=
new
long
[
MAX_DIGITS
];
/** Check if a number is palindromic. */
static
boolean
isPalindrome
(
long
num
)
{
// Consider any single digit to be as palindromic as can be
if
(
num
>=
0
&&
num
<=
9
)
return
true
;
int
nDigits
=
0
;
while
(
num
>
0
)
{
digits
[
nDigits
++]
=
num
%
10
;
num
/=
10
;
}
for
(
int
i
=
0
;
i
<
nDigits
/
2
;
i
++)
if
(
digits
[
i
]
!=
digits
[
nDigits
-
i
-
1
])
return
false
;
return
true
;
}
static
long
reverseNumber
(
long
num
)
{
int
nDigits
=
0
;
while
(
num
>
0
)
{
digits
[
nDigits
++]
=
num
%
10
;
num
/=
10
;
}
long
ret
=
0
;
for
(
int
i
=
0
;
i
<
nDigits
;
i
++)
{
ret
*=
10
;
ret
+=
digits
[
i
];
}
return
ret
;
}
}
While it’s not strictly a numerical solution, Daniel Hinojosa noted that you can use StringBuilder to do the reversal portion, resulting in shorter, more elegant code that is only fractionally slower:
static boolean isPalindrome(long num) { long result = reverseNumber(num); return num == result; } private static long reverseNumber(long num) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(num); return Long.parseLong(stringBuilder.reverse().toString()); }
A full version of his code is in the file PalindromeViaStringBuilder.java
.
People using Java in scientific or large-scale numeric computing may wish to check out the value types forthcoming from “Project Valhalla” in Java. See also this 2019 presentation on Vectors and Numerics on the JVM.
1 For a low-cost source of randomness, check out the now-defunct Lavarand. The process used digitized video of 1970s “lava lamps” to provide “hardware-based” randomness. Fun!
2 Note that an expression consisting entirely of compile-time constants, like Math.PI * 2.1e17
, is also considered to be Strict-FP.
3 Certain values do not work; for example, Ashish Batia reported that this version gets an exception on the value 8989 (which it does).
18.216.171.107