Chapter 5. Numbers

5.0 Introduction

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.

Table 5-1. Numeric types
Built-in type Object wrapper Size of built-in (bits) Contents

byte

Byte

8

Signed integer

short

Short

16

Signed integer

int

Integer

32

Signed integer

long

Long

64

Signed integer

float

Float

32

IEEE-754 floating point

double

Double

64

IEEE-754 floating point

char

Character

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 floats, and 2^64 for doubles. 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.

See Also

The Java Language Specification. The javadoc page for java.lang.Math.

5.1 Checking Whether a String Is a Valid Number

Problem

You need to check whether a given string contains a valid number, and, if so, convert it to binary (internal) form.

Solution

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;
        }
    }

Discussion

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;
            }
    }

See Also

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.

5.2 Converting Numbers to Objects and Vice Versa

Problem

You need to convert numbers to objects and objects to numbers.

Solution

Use the Object Wrapper classes listed in Table 5-1 at the beginning of this chapter.

Discussion

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.

Example 5-1. main/src/main/java/structure/AutoboxDemo.java
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);            1
        System.out.println(result);
    }

    public static Integer foo(Integer i) {
        System.out.println("Object = " + i);
        return Integer.valueOf(123);    2
    }
}
1

Autoboxing: int 42 is converted to Integer(42). Also auto-unboxing: the Integer returned from foo() is auto-unboxed to assign to int result.

2

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);
    }
}

5.3 Taking a Fraction of an Integer Without Using Floating Point

Problem

You want to multiply an integer by a fraction without converting the fraction to a floating-point number.

Solution

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.

Discussion

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 = 2d/3d * 5;  // "normal"
        System.out.println(d3);

        double d4 = (2*5)/3d;   // 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.

5.4 Working With Floating-Point Numbers

Problem

You want to know if a floating-point computation generated a sensible result, as well as comparing and rounding floating-point values.

Solution

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.

Discussion

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.

Comparing Floating-point Values

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
$
Example 5-2. main/src/main/java/numbers/FloatCmp.java
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 NaNs is that you should always make sure values are not NaN before entrusting them to Double.equals().

Rounding

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.

5.5 Formatting Numbers

Problem

You need to format numbers.

Solution

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, 22d/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.

Table 5-2. DecimalFormat pattern characters
Character Meaning

#

Numeric digit (leading zeros suppressed)

0

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
$

Human-Readable Number Formatting

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.

Example 5-3. nmain/src/main/java/numbers/CompactFormatDemo.java
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));
        }
    }

}

Roman Numeral Formatting

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.

See Also

Java I/O (O’Reilly) includes an entire chapter on NumberFormat and develops the subclass ExponentialNumberFormat.

5.6 Converting Among Binary, Octal, Decimal, and Hexadecimal

Problem

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.

Solution

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.

Example 5-4. main/src/main/java/numbers/IntegerBinOctHexEtc.java
        String input = "101010";
        for (int radix : new int[] { 2, 8, 10, 16, 36 }) {
            System.out.print(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
$ 

Discussion

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.

5.7 Operating on a Series of Integers

Problem

You need to work on a range of integers.

Solution

For a contiguous set, use IntStream::range and rangeClosed, or the older for loop.

For discontinuous ranges of numbers, use a java.util.BitSet.

Discussion

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:

Example 5-5. main/src/main/java/numbers/NumSeries.java
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[]
}

5.8 Formatting with Correct Plurals

Problem

You’re printing something like "We used " + n + " items", but in English, “We used 1 items” is ungrammatical. You want “We used 1 item.”

Solution

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.

See Also

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.

5.9 Generating Random Numbers

Problem

You need to generate random numbers in a hurry.

Solution

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.

jcb4 0501
Figure 5-1. Flat (left) and Gaussian (right) distributions

Looks like both PRNG’s do their job!

See Also

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).

5.10 Multiplying Matrices

Problem

You need to multiply a pair of two-dimensional arrays, as is common in mathematical and engineering applications.

Solution

Use the following code as a model.

Discussion

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.

Example 5-6. Matrix.java
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.print("{");
            for (int j=0; j<cols; j++)
                System.out.print(" " + a[i][j] + ",");
            System.out.println("},");
        }
        System.out.println("};");
    }
}

Here is a program that uses the Matrix class to multiply two arrays of ints:

        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);

See Also

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.

5.11 Using Complex Numbers

Problem

You need to manipulate complex numbers, as is common in mathematical, scientific, or engineering applications.

Solution

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.

Example 5-7. main/src/main/java/numbers/Complex.java
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;
    }
}

5.12 Handling Very Large Numbers

Problem

You need to handle integer numbers larger than Long.MAX_VALUE or floating-point values larger than Double.MAX_VALUE.

Solution

Use the BigInteger or BigDecimal values in package java.math, as shown in Example 5-8.

Example 5-8. main/src/main/java/numbers/BigNums.java
        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.

Discussion

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.

Example 5-9. main/src/main/java/numbers/BigNumCalc.java
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.

5.13 Program: TempConverter

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).

Example 5-10. main/src/main/java/numbers/TempConverter.java
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);
            print(i, c);
        }
    }

    public static double cToF(double deg) {
        return ( deg * 9 / 5) + 32;
    }

    public static double fToC(double deg) {
        return ( deg - 32 ) * ( 5d / 9 );
    }

    protected void print(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.

Example 5-11. main/src/main/java/numbers/TempConverter2.java
public class TempConverter2 extends TempConverter {

    public static void main(String[] args) {
        TempConverter t = new TempConverter2();
        t.start();
        t.data();
        t.end();
    }

    @Override
    protected void print(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

5.14 Program: Number Palindromes

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.

Example 5-12. main/src/main/java/numbers/Palindrome.java
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.

See Also

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.171.107