Showing posts with label Math. Show all posts
Showing posts with label Math. Show all posts

Levenshtein distance

Given two strings, return its Levenshtein distance, i.e. the minimum number of character changes needed to mutate the first string in the second one.

You can have a look at this Levenshtein demo to get a better idea of what is expected. Notice that the demo is more complicated than the required solution. We are interested only in the "plain" string type, and we always use weight 1 for indel (insertion/deletion) and substitution operations. Moreover, we don't consider swap.

The Levenshtein distance is often used to show how Dynamic Programming works in a more complicated case than the classic make change problem. Here I present my Java solution based on the Wagner–Fischer algorithm, that asks us to build a bidimensional table generating its values starting from the most basic cases up to the required solution.

The idea of the algorithm is to put on one table axis the first string and the second one on the other one, paying attention to give an extra position at the beginning of both string. So, for instance, if we want to calculate the distance between "one" and "bone", we'll 4 rows and 5 columns (or the other way round).

Then we have to populate the tables. The top-left cell is the easiest one, it represent the case I want to pass from an empty string to another empty one. No change is needed, so I put there a zero.

To understand what I should put in the first row I can think to the case I want to calculate the distance from an empty string to another one size n. Obviously, I would increase by one the value in each cell, representing the cost of inserting a new character.

Similarly, the first column will follow the same structure. Here the increment would represent the deletion of a character.

The generic case is a bit more complicated, since we have to evaluate four different cases. Let's think to the cell (1,1). To decide which value assign to it we need to compare the first elements of both string. If it is the same, we don't have to increase the distance, we just keep the value calculate for the top left case, namely cell (0,0). Otherwise, we need to consider all the three possible editing that would let us arrive there, inserting a new character, deleting or substituting the existing one. Since we want to find the minimum distance, we'll choose the smallest among these values.

Remembering how we filled the first row and column, we should see how to get the value for inserting and deleting, it is just a matter of increasing the value contained on the cell on the left and on the top, respectively. For substitution we have to refer to the top-left cell, as we have done for the equality case.

Once we have grasped the sense of it, building the table is a piece of cake, and what we have to do is just returning the value in the bottom-right cell.

For testing purpose, and for let me better understand how things work, instead of implementing the solution with a single static method, I used a class with a constructor, that does all the dirty job, and a couple of public methods, one that returns the calculated Levenshtein distance and another one that checks the table as generated by the constructor. You could see the full Java code on GitHub, both for the Levenshtein class and its associated test cases, here I publish and comment only the ctor:
private int[][] matrix; // 1

public Levenshtein(String from, String to) { // 2
    matrix = new int[from.length() + 1][to.length() + 1]; // 3
    for (int i = 1; i < matrix[0].length; ++i) { // 4
        matrix[0][i] = i;
    }
    for (int j = 1; j < matrix.length; ++j) {
        matrix[j][0] = j;
    }

    for (int i = 0; i < from.length(); i++) { // 5
        for (int j = 0; j < to.length(); j++) {
            if (from.charAt(i) == to.charAt(j)) { // 6
                matrix[i + 1][j + 1] = matrix[i][j];
            } else { // 7
                int insert = matrix[i][j + 1] + 1;
                int delete = matrix[i + 1][j] + 1;
                int substitute = matrix[i][j] + 1;
                matrix[i + 1][j + 1] = Math.min(Math.min(insert, delete), substitute);
            }
        }
    }
}
1. Here is where I am going to store all the partial solutions and the actual one.
2. I think the first string associated to the rows in matrix, the second one to the columns.
3. The matrix has one row plus one for each character in the first string and a column plus one for each character in the second one.
4. Initialize the zeroth row and column as discussed above. No need to initialize matrix[0][0], the default value 0 is exactly what we want.
5. Loop on all the characters in both strings.
6. It's a match! Put in the relative cell in the matrix the same value stored in the top left cell.
7. Otherwise calculate how much is going to cost to put there the right value by insertion, deletion, substitution, and put there the minimum value among these three ones.

Once built the matrix, the solution appears on the bottom right cell in the matrix.

Go to the full post

Greatest Common Divisor

Given two integers, find their greatest common divisor.

If you don't know the Euclid algorithm, you should get to a piece of code like this:
static public int naive(int a, int b) {
    int result = 1;
    for (int i = 2; i <= Math.min(a, b); ++i) {
        if (a % i == 0 && b % i == 0) {
            result = i;
        }
    }
    return result;
}
This raw idea could be improved considering that it doesn't make any sense to check numbers bigger than the square root of the smaller one (beside itself), as well as the multiple of the already discarded numbers - if 2 is not a good candidate, there is no use in checking any even number.

However, Euclid devised such an elegant and fast converging algorithm that you should have a really good reason to explore alternatives.

Knowing that the gcd of a and b is the same as the gcd of a and a % b, we can write:
public static int euclid(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

To ensure I didn't make any silly mistake when writing my Java code, I have written a couple of simple JUnit test cases. Both files are now on GitHub.

Go to the full post

Last digit of a Fibonacci number

The Fibonacci sequence leads rapidly to such huge values that it gets difficult even to think to a monster like Fibonacci of one million. However, let's say that we have a less mind-boggling problem, calculating just the last digit of it, even if for large input, like one hundred million. This is not a big issue, in the worst case it is to spot a linear time complexity solution, and if we check closely the properties of the sequence, we could devise a costant time one.

Firstly, I write a few test case. I am using JUnit 4, and the assertThat Hamcrest-style assertion:
public class FibonacciTest {
    static final Fibonacci fib = new Fibonacci();

    // ...

    @Test
    public void testLastDigit40() {
        assertThat(fib.lastDigitNaive(40), is(5));
    }

    @Test
    public void testLastDigit327305() {
        assertThat(fib.lastDigit(327_305), is(5));
    }

    @Test
    public void testLastDigit100mio() {
        assertThat(fib.lastDigit(100_000_000), is(5));
    }
It is reasonably easy to calculate Fibonacci(40) = 102_334_155 and, trust me, both Fibonacci(327_305) and Fibonacci(100_000_000) have also a five as their last digit.

Then I write a first implementation for lastDigit() method in my class Fibonacci:
public int lastDigit(int n) {
    if (n < 2) // 1
        return n;

    int previous = 0; // 2
    int current = 1;

    for (int i = 1; i < n; ++i) { // 3
        int older = previous;
        previous = current;
        current = (older + previous) % 10; // 4
    }

    return current;
}
1. Accordingly to the modern definition of this sequence, Fibonacci(0) is 0 and Fibonacci(1) is 1.
2. To calculate the current Fibonacci number we need to keep track of the latest two ones.
3. Let's loop until we reach our target. The previous value will be discarded, current one will become previous, and the current one is calculated.
4. We are lucky, we don't need to store the actual Fibonacci number, it's last digit is enough.

Not a bad solution, however we could do better. We just have to notice that after 60 numbers, the final number in the sequence enter in a loop and repeat the first value. The is an interesting thread on math stackexchange that gives more details on this curious feature.

Let's exploit it to get a better solution:
public int lastDigit(int n) {
    int[] fibLast = new int[60]; // 1
    fibLast[0] = 0;
    fibLast[1] = 1;
    for (int i = 2; i < 60; ++i) { // 2
        fibLast[i] = (fibLast[i - 1] + fibLast[i - 2]) % 10;
    }

    return fibLast[n%60]; // 3
}
1. I am going to do the same job as in the first implementation, however I cache the first sixty values in a raw array.
2. Notice the despicable use of the magic number 60 here and in (1). It should be a well commented constant instead!
3. Thanks to the looping nature of the last cypher in Fibonacci numbers, I just have to apply to modulo operator to extract the correct index in my cached values.

As an obvious performance improvement we should initialize the cache only the first time the method is called. In any case, I think the point is made, so I commit the Fibonacci Java class and its JUnit tester to github.

Go to the full post

The Math object

The JavaScript developer has access to a bunch of commonplace mathematical constants and functions through the Math object. As official reference you should check the ECMA-262 5.1 standard, even if I should confess that I find more readable the Mozilla Developer page.

To calculate a circumference, it is nice to have a PI constant available:
var radius = 1;
var circ = 2 * Math.PI * radius;
console.log("Circumference is ", circ);
The resulting value should be something around 6.283185307179586.

We can convert a number to integer by approximation using round(), or we can use ceil() and floor(), to flatten the result to the left or to the right.
Let's see what happen applying these functions to the natural logarithm of 10:
var value = Math.LN10;
console.log(Math.floor(value), " <= ", value, " <= ", Math.ceil(value));
console.log("Rounding", value, " -> ", Math.round(value));
The max() and min() functions accept in input any (reasonable) number of parameters, and its returned value is the bigger or smaller one in the lot.
var values = [ -42, 5, Math.E ];
console.log(Math.max(values[0], values[1], values[2]));
console.log(Math.min(values[0], values[1], values[2]));
Min is -42, max is 5.

There are also the usual functions to calculate exponential values, Math.exp(), logarithm,

Math.log(), power, Math.pow(), a short cut for square roots, sqrt(), and the common trigonometric functions.

Playing around with JavaScript numerical functions, you can end up getting Infinity, -Infinity, and NaN.
console.log("Divide by zero:", 3 / 0); // Infinity
console.log("Divide by zero:", -3 / 0);  // -Infinity
console.log("Not a number:", 0 / 0);
console.log("Not a number:", Math.sqrt(-2)); // Nan

Go to the full post

Solving systems of linear equations

In the Java Apache Commons Math library there is a package, linear, that is about Linear Algebra. There we could find support to manage systems of linear equation.

A very simple linear system, as shown in the above quoted wikipedia page, features two equations and two variables:
2x + 3y = 6
4x + 9y = 15
Let's solve it through Apache:
RealMatrix coeffs = new Array2DRowRealMatrix(
        new double[][] {{2, 3}, {4, 9}}, false ); // 1
DecompositionSolver ds = new
        LUDecompositionImpl(coeffs).getSolver(); // 2

RealVector consts = new ArrayRealVector(
        new double[] {6, 15}, false ); // 3
RealVector sol = ds.solve(consts); // 4
System.out.println(sol);
1. We create an Array2DRowRealMatrix object passing to it our coefficients as a bidimensional array of double. The second parameter, set to false, says to the ctor not to bother making a local copy of the array, but use it directly. We won't care of the actual matrix type, so we store the result in the interface RealMatrix, root of a hierarchy of matrices including also an implementation for sparse matrix (OpenMapRealMatrix).
2. We should decide which algorithm use to solve the system. Here the chosen one is the LU decomposition. See the Apache documentation for the available alternatives (Cholesky, QR ...). Notice that here we need just to get a solver from the decompositor, so it is created and then forgotten at the end of the same line. But its solver survives and it is kept as a DecompositionSolver interface.
3. Very similarly to (1), we create an ArrayRealVector object for the constants, without copying the double raw array created on the fly, and keep it as a RealVector interface.
4. Calling DecompositionSolver.solve() for the constants vector we get a vector containing the solution. If there is no solution we get an exception instead. So it would be better to try/catch all this code (also the Array2DRowRealMatrix ctor throws unchecked exceptions) to avoid unpleasant surprises. It is easy to make this code crashing. Just change the coefficients and constants so that the system has no solution - just think to the geometrical interpretation of this system, remember that there is no intersection between parallel lines, and try to solve a system where:
RealMatrix coeffs = new Array2DRowRealMatrix(
        new double[][] {{2, 3}, {4, 6}}, false );
RealVector consts = new ArrayRealVector(
        new double[] {6, 12}, false );
You will get instead of a solution a SingularMatrixException.

We can extract from a RealVector the underlying raw double array:
double[] dv = sol.getData();
for(double d : dv)
    System.out.print(d + " ");
System.out.println();
But we can even get rid of all the RealVector objects above, and use directly just raw arrays:
// ...
double[] consts = new double[] {6, 15};
double[] sol = ds.solve(consts);

for(double d : sol)
    System.out.print(d + " ");
System.out.println();

Go to the full post

More random generators

If we need a sequence of pseudo-random numbers in Java we could use the java.util.Random class. For simple requirements this would suffice, but when the game gets tougher it should be worthy have a look at the generators provided by the Apache Commons Math library.

If we want to fill up an array of integer with a random sequence of one digit numbers, we could write:
Random rg = new Random();
rg.setSeed(System.currentTimeMillis()); // 1

for (int i = 0; i < ia.length; ++i) // 2
    ia[i] = rg.nextInt(10); // 3
1. The generator is initialized using the current timestamp. 2. "ia" is an array of integer, supposed to be properly initialized. 3. Each element in the array is set to a random number extracted from the interval [0,9]. For testing purpose it is useful to generate always the same sequence, to do that we can rewrite (1) to set the seed to a constant value:
rg.setSeed(42);
Apache Commons Math makes available a wrapper class that makes handier working with the standard random generator, letting us to easily change the generator to more advanced ones - provided by the library itself or created by ourselves.
RandomData rd = new RandomDataImpl(); // 1
for (int i = 0; i < ia.length; ++i) {
    ia[i] = rd.nextInt(0, 9); // 2
1. The current time is used to seed the generator, if we want to force using a specific seed we should call RandomDataImpl.reSeed(). Unfortunately this method is not part of the RandomData interface, so if we want this functionality be available we are forced to work with the concrete type. 2. Here nextInt() is more flexible, we specify both limit of the interval of values generated (besides, here the last one is included, in the java.util.Random it is excluded). Moreover, this method throws an exception if the second parameter is not bigger than the first one. One last notation, there is no check on the positiveness of the passed parameters (the standard Random throws a IllegalArgumentException if we pass a negative value), but the algorithm is not expected to works correctly for negative values. If we write the code using the Apache wrapper class, we can easily swap the random generator, using an overload of the constructor. So, to use the generator based on the Mersenne Twister instead of the JDK standard, we just have to rewrite (1) in this way:
RandomData rd = new RandomDataImpl(new MersenneTwister());
The Mersenne twister is one of the handful of generator based on the WELL family provided out of the (Apache) box.

Go to the full post

OLSMultipleLinearRegression parabola

We know how to infer a first degree curve (i.e. a straight line) from a bunch of observations using the Ordinary Least Squares estimator provided in the Java Apache Commons Math package.

Things are getting a bit more complicated here, as we try to get as estimation a second degree curve (a parabola).

Assuming that "ols" is a previously defined OLSMultipleLinearRegression object, here is the code that sets its sample data and then extract the relative coefficent estimation:
int vars = 2; // 1
int obs = 3; // 2
double[] data = { 4, 1, 1, /**/ 8, 2, 4, /**/ 14, 3, 9, }; // 3

ols.newSampleData(data, obs, vars); // 4

double[] coe = ols.estimateRegressionParameters();
dumpEstimation(coe); // 5
1. The number of independent variables for a parabola should be 2.
2. As before, we should provide at least one observation more than the vars.
3. Here is the input data. First component is y, than we have x, and then x square. These observations are lazily taken calculating y = x^2 + x + 2, as you could have guessed, so we would expect to get back as coefficients values close to (2, 1, 1).
4. I didn't try/catch, assuming that the caller of this code would do that for me. Actually, all the exceptions thrown by this package are unchecked (derived from RuntimeException), so we are not forced to try/catch or declare them in the method signature - and we can simply accept the risk of a sudden death of our application.
5. We have seen this little testing function in the previous post, it works fine here too.

Does the flattened data array bother you? Maybe not in this so simple example, but in a more real scenario could be cumbersome to organize data accordingly to this model. It could be useful to place y's in a unidimensional array, and x's in a separate bidimensional one. There is a OLSMultipleLinearRegression.newSampleData() overload that works right in this way:
double[] ys = { 4, 8, 14 };
double[][] xs = new double[][] { {1, 1}, {2, 4}, {3, 9} };
ols.newSampleData(ys, xs);

double[] coe = ols.estimateRegressionParameters();
dumpEstimation(coe);
This piece of code should be equivalent to what we have seen above.

Go to the full post

The simplest OLSMultipleLinearRegression example ever

Assuming that the reader knows what Multivariate Linear Regression by Ordinary Least Squares is, and that he would like to use it in Java by means of OLSMultipleLinearRegression, class that is part of the Apache Commons Math library, let's see the simplest example of its usage I could think of.

I have two observations, one variable, and I want to get the description of an interpolating curve:
OLSMultipleLinearRegression ols = new OLSMultipleLinearRegression();

double[] data = { 2, 1, 4, 2 }; // 1
int obs = 2;
int vars = 1; // 2
try {
    ols.newSampleData(data, obs, vars); // 3
}
catch(IllegalArgumentException e) {
    System.out.print("Can't sample data: ");
    e.printStackTrace();
    return;
}

double[] coe = null;
try {
    coe = ols.estimateRegressionParameters(); // 4
}
catch(IllegalArgumentException | InvalidMatrixException e) { // 5
    System.out.print("Can't estimate parameters: ");
    e.printStackTrace();
    return;
}

dumpEstimation(coe);
  1. The input data is flattened in an array, where all the observations are stored one after the other, respecting the convention of having first the y and then the x component.
  2. We should have more observations than variables, so this is the minimal case. Having just one variable, we'll get a straight line as a result.
  3. The functions performs a few check on the passed data before working with them, if the number of variables is not bigger than the number of observations, or if there are less values than expected in the array, an IllegalArgumentException is thrown.
  4. The resulting curve parameters are returned in a double array. In case of failure an exception is thrown.
  5. Cool Java 7 feature, we can group all the exceptions that requires the same management in a single block. In previous Java version, a specific catch is required for each exception type.
The last line is a call to a testing function that gives some feedback to the user. It calls another short function that actually calculates the estimated y value given a specific x:
private void dumpEstimation(double[] coe) {
    if(coe == null)
        return;

    for(double d : coe)
        System.out.print(d + " ");
    System.out.println();

    System.out.println("Estimations:");
    System.out.println("x = 1, y = " + calculateEstimation(1, coe));
    System.out.println("x = 2, y = " + calculateEstimation(2, coe));
    System.out.println("x = 3, y = " + calculateEstimation(3, coe));
    System.out.println("x = 4, y = " + calculateEstimation(4, coe));
}

private double calculateEstimation(double x, double[] coe) {
    double result = 0;
    for(int i = 0; i < coe.length; ++i)
        result += coe[i] * Math.pow(x, i); // 1
    return result;
}
  1. The most interesting line in this testing code. It shows how the coefficients are stored in the array returned from OLSMultipleLinearRegression.estimateRegressionParameters(). As we see, the coefficient 'i' is relative to the 'i'-th power of x.
The expected output is:
-0.0 2.0 
Estimations:
x = 1, y = 2.0
x = 2, y = 4.0
x = 3, y = 6.0
x = 4, y = 8.0

Go to the full post

SummaryStatistics vs. DescriptiveStatistics

Apache Commons Math makes available the classes DescriptiveStatistics and SummaryStatistics, both derived from the interface StatisticalSummary (there are a few more classes in the same hierarchy, that are not in this post spotlight). Both of them are used to get basic univariate statistics, but there are a few reason that should help us to decide which one is the most suitable for a specific task.

Let's start talking about commonality. Both of them are not synchronized, if you really need a thread-safe implementation, check out for SynchronizedDescriptiveStatistics and SynchronizedSummaryStatistics, and both of them implement all the basic functionality defined in StatisticalSummary.

It is a handful of methods that should look pretty straightforward to the reader having just some basic statistics knowledge:
double getMean(); // arithmetic mean
double getVariance();
double getStandardDeviation();
double getMax();
double getMin();
long getN(); // number of available values
double getSum(); // sum of the values
All this methods return NaN when called if no values have been added to the object, with the obvious exception of getN() that returns 0.

The substantial difference is that SummaryStatistics does not store data values in memory, resulting being sleeker and leaner than DescriptiveStatistics. On the other hand, DescriptiveStatistics makes available some more functionality to the user. So, if what you need is in StatisticalSummary, you can manage huge collection of data with SummaryStatistics and happily avoid to pay a large price in terms of memory usage.

There are then a few common methods that are defined for both SummaryStatistics and DescriptiveStatistics, even though they are not part of the commonly implemented interface StatisticalSummary.

To load the data we use public void addValue(double value), that could be called like this, where generator is a Random object previously initialized:
for(int i = 0; i < 1000; ++i) {
    stats.addValue(generator.nextDouble());
}
From object of both classes we can get the sum of the squares, getSumsq(), and the geometric mean, getGeometricMean(). Sometimes it is useful to reset the values on which we are working, and this is done by calling clear().

Only for SummaryStatistics are defined getSumOfLogs() and getSecondMoment().

Only for DescriptiveStatistics are available:

void removeMostRecentValue(): discards just the last value inserted in the underlying dataset or throws an exception.
double replaceMostRecentValue(double v): replaces the last inserted value or throws an exception.
double getSkewness(): the skewness is a measure of the current distribution asymmetry.
double getKurtosis(): the kurtosis is a measure of the current distribution protrusion.
double[] getValues(): creates a copy of the current data set.
double[] getSortedValues(): creates a sorted copy of the current data set.
double getElement(int index): gets a specific element or throws an exception.
double getPercentile(double p): an estimation of the requested percentile, or throws an exception.

Window size

When we have no idea of how many values could be entered, it could be dangerous using DescriptiveStatistics in its default mode, that let the underlying data collection growing without any limit. Better to define the dimension of the "window" we want to work with using setWindowSize(int windowSize). What happens when we reach the limit is that the oldest value is discarded to let room for the new entry. If you wonder what is the current size, you can check it through getWindowSize() that returns, as an int, its current value. The "no window" value is represented by DescriptiveStatistics.INFINITE_WINDOW, defined as -1.

Go to the full post

Random expectation

There is not much one could reasonably say about a single random extraction. But we have a more defined expectation on the mean of a series of random extractions.

The Java Random class makes available a few different pseudo-random distribution from which we can extract variables and play with them. Here we are going to use values returned by nextDouble(), method that returns a value picked up from a uniform distribution between 0.0 and 1.0, and nextGaussian(), that refers to a Gaussian ("normal") distribution with mean 0 and standard deviation 1.

To spice things a bit, we will also manipulate the gaussian values to get a square gaussian distribution.

The mean for the variables generated from Random.nextDouble() should tend to 0.5, as we should find out running a few times this function with increasing values of "n":
double expectU(int n) {
    double sum =0;
    for(int i =0; i < n; ++i)
        sum += generator.nextDouble();
    return sum/n;
}
Doesn't change much for Random.nextGaussian(), but that it should tend to a mean of 0.0:
double expectG(int n) {
    double sum =0;
    for(int i =0; i < n; ++i)
        sum += generator.nextGaussian();
    return sum/n;
}
A bit more interesting the square gaussian case, since we provide an adaptor from Random.nextGaussian():
double squareGaussian() {
    return Math.pow(generator.nextGaussian(), 2);
}

double expectSG(int n) {
    double sum =0;
    for(int i =0; i < n; ++i)
        sum += squareGaussian();
    return sum/n;
}
In this last test we should see how, for increasing values of n, the resulting expected mean value should tend to 1.0.

Go to the full post

Using random to generate pi

It could look counterintuitive, but we can use a pseudo-random, uniformly distributed, sequence to find out pi. One could asks why on the Earth we should do something like that when any reasonable programming environment provides it off the box - in Java it is a constant in the Math class, Math.PI a double set to 3.14159265358979323846, as one could expect.

OK, forget about the how much it looks reasonable to calculate pi in this way, what it is interesting it is the general idea behind.

Think to a square with a circle inscribed in it. The area of the circle is pi*radius*radius. The area of the square is (2*radius)*(2*radius).

Now think to throw arrows to the square. Assuming the result is an homogeneous distribution, we should get that the ratio between the arrows inside the circle and all the arrows that reached the square should be the same of the ratio between the areas of the two figures.

So: pi*radius*radius / (2*radius)*(2*radius) = arrows in the circle / arrows in the square

It should be easy to get from that: pi = 4 * arrows in the circle / arrows in the square

Let's implement it in Java, simplifying it a bit working on just a quarter of the circle and square, so that we would use just values in the 0, 1 interval:
import java.util.Random;

public class RandomPi {
    private Random generator;

    public RandomPi() {
        generator = new Random(System.currentTimeMillis()); // 1
    } 

    public double pi(int n) { // 2
        int hit = 0;
        for(int i = 0; i < n; ++i) {
            double x = generator.nextDouble();
            double y = generator.nextDouble();

            if(Math.sqrt(x*x + y*y) < 1) // 3
                ++hit;
        }        
        return 4.0 * hit / n; // 4
    }
}
1. The random generator is initialized passing the current time, so to have a different sequence for each run of this application.
2. Our pi generator asks in input for the number of "arrows" that we throw. Be prepared of using an high value to get a reasonable approximation.
3. If the distance of the randomly generated coordinates from the center (0, 0) is less than 1, the arrow is in the circle, so we increase the counter.
4. We apply our formula, as seen above. We explicitly use 4 as a double value, otherwise we would have a division between integers, giving as result an integer, hardly what we really want.

Go to the full post