What is Apache.Commons?
Apache Commons is a fairly large and serious project focused on creating cool Java libraries. Or “a large set of small Java utilities” for various purposes. Some of the Apache Commons utilities underpin a number of very well-known projects such as Tomcat, Hibernate, and others.
Apache Commons Math, as you might guess from the name, is a library for a wide variety of mathematical operations. Let’s see what sections it consists of.
Commons Math is divided into sixteen subpackages, based on functionality provided.
- org.apache.commons.math4.stat – statistics, statistical tests
- org.apache.commons.math4.analysis – root finding, integration, interpolation, polynomials
- org.apache.commons.math4.random – random numbers, strings and data generation
- org.apache.commons.math4.special – special functions (Gamma, Beta)
- org.apache.commons.math4.linear – matrices, solving linear systems
- org.apache.commons.math4.util – common math/stat functions extending java.lang.Math
- org.apache.commons.math4.complex – complex numbers
- org.apache.commons.math4.distribution – probability distributions
- org.apache.commons.math4.fraction – rational numbers
- org.apache.commons.math4.transform – transform methods (Fast Fourier)
- org.apache.commons.math4.geometry – geometry (Euclidean spaces and Binary Space Partitioning)
- org.apache.commons.math4.optim – function maximization or minimization
- org.apache.commons.math4.ode – Ordinary Differential Equations integration
- org.apache.commons.math4.genetics – Genetic Algorithms
- org.apache.commons.math4.fitting – Curve Fitting
- org.apache.commons.math4.ml – Machine Learning
As you can see, this is a great set for a scientist, and here you can find very useful functions for any area of mathematics – numerical analysis, mathematical analysis, complex analysis, algebra, linear algebra, geometry, differential equations. Of course, a detailed review of this library requires several dozen articles. Here, we will look at just a few useful units and methods for those who study programming and want to work in knowledge-intensive industries. This article is more aboutsome analogues of the Math standard library, as well as classes for working with complex numbers and fractions.
By the way, Apache.Commons is an open source project. This means that you can fully study the code of any class or method of the library, freely use them for your projects, and also change the code within them.
org.apache.commons.math4.util
In this set of utilities you will find implementations of functions familiar from the Math package, as well as a number of others. First of all, I would like to consider the class FastMath, in which you can find methods that can be used instead of the standard Math and StrictMath classes for large scale computation. Developers of the Math package, there are methods here that Multiply two numbers, detecting overflows.
static int
|
multiplyExact(int a, int b) Multiply two numbers, detecting overflows. |
static long
|
multiplyExact(long a, long b) Multiply two numbers, detecting overflows. |
Here, you can also find some inverse trigonometric functions that are not in the Math package. For example, hyperbolic arc functions:
- asinh(double)
- acosh(double)
- atanh(double)
Sure, we can write such methods ourselves, guided either by known mathematical formulas or by numerical methods. Here is the simplest version without checks:
public static double asinh(double x){ return Math.log(x + Math.sqrt(Math.pow(x, 2) + 1)); }
If you look into the open source Apache Commons FastMath class (for example, from IntelliJ IDEA IDE, Ctrl (Cmd) + Left click), you will see the following picture:
public static double asinh(double a) { boolean negative = false; if (a < 0.0D) { negative = true; a = -a; } double absAsinh; if (a > 0.167D) { absAsinh = log(sqrt(a * a + 1.0D) + a); } else { double a2 = a * a; if (a > 0.097D) { absAsinh = a * (1.0D - a2 * (0.3333333333333333D - a2 * (0.2D - a2 * (0.14285714285714285D - a2 * (0.1111111111111111D - a2 * (0.09090909090909091D - a2 * (0.07692307692307693D - a2 * (0.06666666666666667D - a2 * 0.058823529411764705D * 0.9375D) * 0.9285714285714286D) * 0.9166666666666666D) * 0.9D) * 0.875D) * 0.8333333333333334D) * 0.75D) * 0.5D); } else if (a > 0.036D) { absAsinh = a * (1.0D - a2 * (0.3333333333333333D - a2 * (0.2D - a2 * (0.14285714285714285D - a2 * (0.1111111111111111D - a2 * (0.09090909090909091D - a2 * 0.07692307692307693D * 0.9166666666666666D) * 0.9D) * 0.875D) * 0.8333333333333334D) * 0.75D) * 0.5D); } else if (a > 0.0036D) { absAsinh = a * (1.0D - a2 * (0.3333333333333333D - a2 * (0.2D - a2 * (0.14285714285714285D - a2 * 0.1111111111111111D * 0.875D) * 0.8333333333333334D) * 0.75D) * 0.5D); } else { absAsinh = a * (1.0D - a2 * (0.3333333333333333D - a2 * 0.2D * 0.75D) * 0.5D); } } return negative ? -absAsinh : absAsinh; }
The code is certainly not the most beautiful, and it will be quite difficult for a third-party developer to figure out why these particular coefficients were chosen. As for good, our own method requires some refinement, checking the scope and so on. But now we will not do this. Let’s take a better example.
import org.apache.commons.math3.util.FastMath; public class MyMathTest { public static double asinh(double x){ return Math.log(x + Math.sqrt(Math.pow(x, 2) + 1)); } public static double asinh2(double x){ return FastMath.log(x + FastMath.sqrt(FastMath.pow(x,2) +1)); } public static void main(String[] args) { int x = 5; System.out.println("asins:"); System.out.println(asinh(x)); System.out.println(asinh2(x)); System.out.println(FastMath.asinh(x)); } }
Here, we’ve created two custom methods for calculating the hyperbolic inverse sine using the Math and FastMath functions, as well as the implementation of the method in FastMath.
asins: 2.3124383412727525 2.3124383412727525 2.3124383412727525
As you can see, the result is exactly the same, and this is not surprising. But how to check the effectiveness of the methods? With such simple examples, we will not succeed.
Let’s try to compare the performance of, for example, two methods for calculating sines. How can I do that? For example, create a loop and add up the sum of randomized elements that are passed to the sin function. Why randomized? In order for the arguments to be different, it may happen that on some of the arguments one function will work better, the other worse. For the purity of the experiment, it is better to give the same arguments to the input, that is, set the Random seed explicitly (just remember that Random itself can work longer than sin). Let’s create a small array, for example, of 64 elements and fill it with random elements, and then, in a loop, give this array to both sin methods. Here is the pseudocode of the main idea without details:
double[] capturedRandom = new double[64] ; capturedRandom[...] = random() * 2*PI; double sumAngle = 0.0, sumSin = 0.0; for(int i = 0; i < 10000 ; ++i) { for(int j = 0; j < 64 ; ++j) { double angle = capturedRandom[j]; sumAngle += angle; sumSin += Math.sin(angle) } }
At the same time, it is important that the result of the loop work goes somewhere, because if you simply call the sine function in the loop, but do not add or write it anywhere, the optimizer can generally throw this code out as unnecessary, and the whole loop as unnecessary, and then it can exit incorrect result (it will seem that everything works instantly, even regardless of the number of iterations in the loop). It is also desirable to take several measurements in a row, without hibernating the JVM. Let’s write such a program.
import org.apache.commons.math3.util.FastMath; public class MathTest { private static double[] randomAngles = fillRandomAngles(64); private static double[] fillRandomAngles(int count) { double[] angles = new double[count]; for (int i = 0; i < count; ++i) { angles[i] = Math.random() * Math.PI * 2; } return angles; } private static double measureMathSin(String introMessage, double[] angles, int iterationsCount) { int totalIterations = angles.length * iterationsCount; double anglesSum = 0.0, sinSum = 0.0; long startTime = System.nanoTime(); for (int i = 0; i < iterationsCount; ++i) { for (double angle : angles) { anglesSum += angle; sinSum += Math.sin(angle); } } long endTime = System.nanoTime(); System.out.println(introMessage + " anglesSum = " + anglesSum + " sinSum = " + sinSum); return (double) (endTime - startTime) / totalIterations; } private static double measureFastMathSin(String introMessage, double[] angles, int iterationsCount) { int totalIterations = angles.length * iterationsCount; double anglesSum = 0.0, sinSum = 0.0; long startTime = System.nanoTime(); for (int i = 0; i < iterationsCount; ++i) { for (double angle : angles) { anglesSum += angle; sinSum += FastMath.sin(angle); } } long endTime = System.nanoTime(); System.out.println(introMessage + " anglesSum = " + anglesSum + " sinSum = " + sinSum); return (double) (endTime - startTime) / totalIterations; } public static void main(String[] args) throws Exception { System.out.println("Run recurrent measurements of Math.sin:"); for (int m = 0; m < 15; ++m) { double mathPerformanceRate = measureMathSin("lang math measurement(" + m + "):", randomAngles, 100_000); System.out.println("mathPerformanceRate = " + mathPerformanceRate); Thread.sleep(1000); double fastMathPerformanceRate = measureFastMathSin("fast math measurement(" + m + "):", randomAngles, 100_000); System.out.println("fastMathPerformanceRate = " + fastMathPerformanceRate); Thread.sleep(1000); } } }
Here is the output:
"C:\Program Files\Java\jdk-13.0.2\bin\java.exe" tests.MathTest Run recurrent measurements of Math.sin: lang math measurement(0): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 15.360328125 fast math measurement(0): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 30.992515625 lang math measurement(1): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 11.793078125 fast math measurement(1): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 23.971203125 lang math measurement(2): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.642640625 fast math measurement(2): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.390828125 lang math measurement(3): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.648453125 fast math measurement(3): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 23.799546875 lang math measurement(4): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.931828125 fast math measurement(4): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 23.020265625 lang math measurement(5): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 11.12209375 fast math measurement(5): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.92071875 lang math measurement(6): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.78703125 fast math measurement(6): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.76290625 lang math measurement(7): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.539203125 fast math measurement(7): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 23.134984375 lang math measurement(8): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.80353125 fast math measurement(8): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 23.21146875 lang math measurement(9): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.78996875 fast math measurement(9): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.454765625 lang math measurement(10): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 11.12425 fast math measurement(10): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.80259375 lang math measurement(11): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.803765625 fast math measurement(11): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.57290625 lang math measurement(12): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.763 fast math measurement(12): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.430453125 lang math measurement(13): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 10.684859375 fast math measurement(13): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.68584375 lang math measurement(14): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 mathPerformanceRate = 11.641078125 fast math measurement(14): anglesSum = 1.8562502857676055E7 sinSum = 361279.52945610177 fastMathPerformanceRate = 22.545
Wow! “Native” math.sin() looks 2 times better than “fast”. Perhaps this is because “native” just calls the machine command, and “fast” does something in Java code.Approximately the same results were shown by other comparisons of functions from the FastMath.
Working with fractions
Sometimes working with simple fractions in programming is not so easy, but Apache Commons Math has the Fraction and BigFraction class for this. Let’s give an example in which we add fractions, take the modulus, and also give the reciprocal of our fraction (reciprocal () method).
import org.apache.commons.math3.fraction.Fraction; public class MyMathTest { public static void main(String[] args) { //here we’ve got three fraction numbers Fraction fraction1 = new Fraction(-0.25); Fraction fraction2 = new Fraction(1,4); Fraction fraction3 = new Fraction(3,4); System.out.println(fraction1.add(fraction2)); // subtract two fractions System.out.println(fraction1.subtract(fraction2)); // absolute value System.out.println(fraction1.abs()); System.out.println(fraction2.reciprocal()); // reciprocal System.out.println(fraction3.reciprocal()); } }
The output is:
0 -1 / 2 1 / 4 4 4 / 3
As you see, we can work with fractions in both decimal and ordinary form.
Complex numbers
Complex numbers open up very broad possibilities for working with scientific calculations. They are used literally everywhere – from classical physics to complex mathematical models associated with technical inventions, in working with quantum mechanics and fractals.
The Complex Numbers library has arithmetic functions, the complex conjugation function, and many others. You can use a complex number as an argument to a math function. A complex number is written as follows:
Complex complex1 = new Complex(1.0, 5.0);
Where the first argument (1) is the real part and the second (5) is imaginary. So here we have a complex number a + 5i. Here is a small example.
import org.apache.commons.math3.complex.Complex; public class MyMathTest3 { public static void main(String[] args) { Complex complex1 = new Complex(1.0, 5.0); Complex complex2 = new Complex(3.5, 7); System.out.println("Adding 2 complex numbers: " + complex1.add(complex2)); System.out.println("Dividing 2 complex numbers:" + complex1.divide(complex2)); System.out.println("complex conjugate of a + 5i = " + complex1.conjugate()); System.out.println("sin (a + 5i) = " + complex1.sin()); } }
The output is:
Adding 2 complex numbers: (4.5, 12.0) Dividing 2 complex numbers:(0.6285714285714286, 0.17142857142857143) complex conjugate of a + 5i = (1.0, -5.0) sin (a + 5i) = (62.44551846769654, 40.0921657779984)
Let me remind you that the complex conjugation of a number a + bi is the number a – bi.
Conclusion
In this article, we have only gone through some useful classes for students and scientists. Of course, such a huge library requires more in-depth research. Fortunately, all of its code is open and you can dive into it very deeply, understanding how the code works. In fact, working with fractions and complex numbers in Apache Commons Math looks interesting. Also noteworthy are tools for analysis, algebra, statistics, and probability that go beyond the scope of this article. But the standard mathematical functions, judging by my experiments, it is better to use native ones. I ran several experiments like the one I showed in this article, and they all showed the same result: FastMath not as fast as “ordinary” Math.