In a programming contest, I summarized the pitfalls I was addicted to and how to avoid them, as well as my own reflection on the error in Java's Math.sqrt method.
The sqrt method of Java's Math class is a method that can find the square root of a numerical value given in double type, and is often used when performing numerical calculations.
However, of course, there are some errors in the results.
In many cases, the result is negligible, but in some cases the error is non-negligible.
In this article, I'll show you some of the cases I've encountered and give examples of how to avoid them.
In a programming contest I participated in, I needed to write the following code.
$ a \ leq 10 ^ {18} $ When given a positive integer $ a $, we want to find the maximum value of $ b $ that satisfies $ b ^ {2} <a $.
Here is the code I wrote first. (Added on April 9, 2018) As you pointed out in the comments, this code is a little wasteful. "Sample code corrected version" has been added at the end.
sample1.java(Excerpt)
private static long floorSqrt (long a) {
long b = (long)Math.sqrt(a);
if (b*b == a) {
return b-1;
} else {
return b;
}
}
I will explain briefly.
At first glance it seems to work, but the above code doesn't work.
(Added on April 9, 2018) As you pointed out in the comments, this code is a little wasteful. "Sample code corrected version" has been added at the end.
sample1.java
public class Sample1 {
public static void main(String[] args) {
System.out.println("lessThanSqrt (99)=" + lessThanSqrt (99));
System.out.println("lessThanSqrt (100)=" + lessThanSqrt (100));
System.out.println("lessThanSqrt (999999999)=" + lessThanSqrt (9999999999L));
System.out.println("lessThanSqrt (10000000000)=" + lessThanSqrt (10000000000L));
System.out.println("lessThanSqrt (999999999999999999)="+lessThanSqrt (999999999999999999L));
System.out.println("lessThanSqrt (1000000000000000000)=" + lessThanSqrt (1000000000000000000L));
}
private static long lessThanSqrt (long a) {
long b = (long)Math.sqrt(a);
if (b*b == a) {
return b-1;
} else {
return b;
}
}
The execution result of the above sample code is as follows.
lessThanSqrt (99)=9
lessThanSqrt (100)=9
lessThanSqrt (999999999)=99999
lessThanSqrt (10000000000)=99999
lessThanSqrt (999999999999999999)=1000000000
lessThanSqrt (1000000000000000000)=999999999
If the value is extremely high, the result is strange.
The cause of this can be understood by looking at the execution result of the sample code below.
sample1_1.java
public class Sample1_1 {
public static void main(String[] args) {
System.out.println(Math.sqrt(99999999999999L));
System.out.println(Math.sqrt(100000000000000L));
System.out.println(Math.sqrt(9999999999999999L));
System.out.println(Math.sqrt(10000000000000000L));
System.out.println(Math.sqrt(999999999999999999L));
System.out.println(Math.sqrt(1000000000000000000L));
}
}
Execution result
9999999.99999995
1.0E7
1.0E8
1.0E8
1.0E9
1.0E9
Looking at the above results, if $ n> 8 $, the results of $ \ sqrt {10 ^ {2n}} $ and $ \ sqrt {10 ^ {2n} -1} $ are the same. I am.
This is a double type precision issue.
As for the precision of the double type, since only about 15 digits of information can be retained, it is not possible to retain information with a larger number of digits, resulting in an error from the original value.
In most cases, the effect is small, but if the number has 9 decimal places, the error may change the number in the integer part.
The square root calculation for large numbers is prone to the above situations.
Below is the code that takes into account the problem of sample code 1 and adds the logic to correct the error. (Added on April 9, 2018) As you pointed out in the comments, this code is a little wasteful. "Sample code corrected version" has been added at the end.
sample2.java
public class Sample2 {
public static void main(String[] args) {
System.out.println("lessThanSqrt (99)=" + lessThanSqrt (99));
System.out.println("lessThanSqrt (100)=" + lessThanSqrt (100));
System.out.println("lessThanSqrt (999999999)=" + lessThanSqrt (9999999999L));
System.out.println("lessThanSqrt (10000000000)=" + lessThanSqrt (10000000000L));
System.out.println("lessThanSqrt (999999999999999999)="+lessThanSqrt (999999999999999999L));
System.out.println("lessThanSqrt (1000000000000000000)=" + lessThanSqrt (1000000000000000000L));
}
private static long lessThanSqrt (long a) {
long b = longSqrt(a);
if (b*b == a) {
return b-1;
} else {
return b;
}
}
private static long longSqrt (long a) {
long b = (long)Math.sqrt(a);
//If the obtained value is squared and exceeds the original value
//Because the error is 1 larger
//Returns the value minus 1 to correct the error
if(b*b > a) {
b--;
}
return b;
}
}
Execution result
lessThanSqrt (99)=9
lessThanSqrt (100)=9
lessThanSqrt (999999999)=99999
lessThanSqrt (10000000000)=99999
lessThanSqrt (999999999999999999)=999999999
lessThanSqrt (1000000000000000000)=999999999
You can now get the correct results.
Not limited to + sqrt, in general, when performing floating-point type calculations, it is necessary to consider errors. If you don't usually handle floating point types too much, you should be careful because consideration tends to be omitted.
Here is a correction of the useless part of the sample code to an efficient code.
Sample1Modified.java(Excerpt)
private static long lessThanSqrt (long a) {
return (long)Math.sqrt(a-1);
}
Sample2Modified.java(Excerpt)
private static long lessThanSqrt (long a) {
return longSqrt(a-1);
}
It is enough to take $ \ sqrt {a-1} $ without dividing the case.