Im writing a function that implements the following expression (1/n!)*(1!+2!+3!+...+n!).
The function is passed the arguement n and I have to return the above statement as a double, truncated to the 6th decimal place. The issue im running into is that the factorial value becomes so large that it becomes infinity (for large values of n).
Here is my code:
public static double going(int n) {
double factorial = 1.00;
double result = 0.00, sum = 0.00;
for(int i=1; i<n+1; i++){
factorial *= i;
sum += factorial;
}
//Truncate decimals to 6 places
result = (1/factorial)*(sum);
long truncate = (long)Math.pow(10,6);
result = result * truncate;
long value = (long) result;
return (double) value / truncate;
}
Now, the above code works fine for say n=5 or n= 113, but anything above n = 170 and my factorial and sum expressions become infinity. Is my approach just not going to work due to the exponential growth of the numbers? And what would be a work around to calculating very large numbers that doesnt impact performance too much (I believe BigInteger is quite slow from looking at similar questions).
You can solve this without evaluating a single factorial.
Your formula simplifies to the considerably simpler, computationally speaking
1!/n! + 2!/n! + 3!/n! + ... + 1
Aside from the first and last terms, a lot of factors actually cancel, which will help the precision of the final result, for example for 3! / n! you only need to multiply 1 / 4 through to 1 / n. What you must not do is to evaluate the factorials and divide them.
If 15 decimal digits of precision is acceptable (which it appears that it is from your question) then you can evaluate this in floating point, adding the small terms first. As you develop the algorithm, you'll notice the terms are related, but be very careful how you exploit that as you risk introducing material imprecision. (I'd consider that as a second step if I were you.)
Here's a prototype implementation. Note that I accumulate all the individual terms in an array first, then I sum them up starting with the smaller terms first. I think it's computationally more accurate to start from the final term (1.0) and work backwards, but that might not be necessary for a series that converges so quickly. Let's do this thoroughly and analyse the results.
private static double evaluate(int n){
double terms[] = new double[n];
double term = 1.0;
terms[n - 1] = term;
while (n > 1){
terms[n - 2] = term /= n;
--n;
}
double sum = 0.0;
for (double t : terms){
sum += t;
}
return sum;
}
You can see how very quickly the first terms become insignificant. I think you only need a few terms to compute the result to the tolerance of a floating point double. Let's devise an algorithm to stop when that point is reached:
The final version. It seems that the series converges so quickly that you don't need to worry about adding small terms first. So you end up with the absolutely beautiful
private static double evaluate_fast(int n){
double sum = 1.0;
double term = 1.0;
while (n > 1){
double old_sum = sum;
sum += term /= n--;
if (sum == old_sum){
// precision exhausted for the type
break;
}
}
return sum;
}
As you can see, there is no need for BigDecimal &c, and certainly never a need to evaluate any factorials.
You could use BigDecimal like this:
public static double going(int n) {
BigDecimal factorial = BigDecimal.ONE;
BigDecimal sum = BigDecimal.ZERO;
BigDecimal result;
for(int i=1; i<n+1; i++){
factorial = factorial.multiply(new BigDecimal(i));
sum = sum.add(factorial);
}
//Truncate decimals to 6 places
result = sum.divide(factorial, 6, RoundingMode.HALF_EVEN);
return result.doubleValue();
}
Related
One of the first things we learn in floating point arithmetics is how rounding error plays a crucial role in double summation. Let's say we have an array of double myArray and we want to find the mean. What we could trivially do is:
double sum = 0.0;
for(int i = 0; i < myArray.length; i++) {
sum += myArray[i];
}
double mean = (double) sum/myArray.length;
However, we would have rounding error. This error can be reduced using other summation algorithm such as the Kahan one (wiki https://en.wikipedia.org/wiki/Kahan_summation_algorithm).
I have recently discovered Java Streams (refer to: https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) and in particular DoubleStream (see: https://docs.oracle.com/javase/8/docs/api/java/util/stream/DoubleStream.html).
With the code:
double sum = DoubleStream.of(myArray).parallel().sum();
double average = (double) sum/myArray.length;
we can get the average of our array. Two advantages are remarkable in my opinion:
More concise code
Faster as it is parallelized
Of course we could also have done something like:
double average = DoubleStream.of(myArray).parallel().average();
but I wanted to stress the summation.
At this point I have a question (which API didn't answer): is this method sum() numerically stable? I have done some experiments and it appears to be working fine. However I am not sure is at least good as the Kahan algorithm. Any help really welcomed!
The documentation says it:
Returns the sum of elements in this stream. Summation is a special
case of a reduction. If floating-point summation were exact, this
method would be equivalent to:
return reduce(0, Double::sum);
However, since floating-point summation is not exact, the above code
is not necessarily equivalent to the summation computation done by
this method.
Have you considered using BigDecimal to perform exact results?
Interesting, so I implemented the Kahan variant of Klein, mentioned in the wikipedia article. And a Stream version of it.
The results are not convincing.
double[] values = new double[10_000];
Random random = new Random();
Arrays.setAll(values, (i) -> Math.atan(random.nextDouble()*Math.PI*2) * 3E17);
long t0 = System.nanoTime();
double sum1 = DoubleStream.of(values).sum();
long t1 = System.nanoTime();
double sum2 = DoubleStream.of(values).parallel().sum();
long t2 = System.nanoTime();
double sum3 = kleinSum(values);
long t3 = System.nanoTime();
double sum4 = kleinSumAsStream(values);
long t4 = System.nanoTime();
System.out.printf(
"seq %f (%d ns)%npar %f (%d ns)%nkah %f (%d ns)%nstr %f (%d ns)%n",
sum1, t1 - t0,
sum2, t2 - t1,
sum3, t3 - t2,
sum4, t4 - t3);
An a non-stream version of modified Kahan:
public static double kleinSum(double[] input) {
double sum = 0.0;
double cs = 0.0;
double ccs = 0.0;
for (int i = 0; i < input.length; ++i) {
double t = sum + input[i];
double c = Math.abs(sum) >= Math.abs(input[i])
? (sum - t) + input[i]
: (input[i] - t) + sum;
sum = t;
t = cs + c;
double cc = Math.abs(cs) >= Math.abs(c)
? (cs - t) + c
: (c - t) + cs;
cs = t;
ccs += cc;
}
return sum + cs + ccs;
}
A Stream version:
public static double kleinSumAsStream(double[] input) {
double[] scc = DoubleStream.of(input)
.boxed()
.reduce(new double[3],
(sumCsCcs, x) -> {
double t = sumCsCcs[0] + x;
double c = Math.abs(sumCsCcs[0]) >= Math.abs(x)
? (sumCsCcs[0] - t) + x
: (x - t) + sumCsCcs[0];
sumCsCcs[0] = t;
t = sumCsCcs[1] + c;
double cc = Math.abs(sumCsCcs[1]) >= Math.abs(c)
? (sumCsCcs[1] - t) + c
: (c - t) + sumCsCcs[1];
sumCsCcs[1] = t;
sumCsCcs[2] += cc;
return sumCsCcs;
},
(scc1, scc2) -> new double[] {
scc2[0] + scc1[0],
scc2[1] + scc1[1],
scc2[2] + scc1[2]});
return scc[0] + scc[1] + scc[2];
}
Mind that the times would only be evidence, when a microworkbench would have been used.
However one still sees the overhead of a DoubleStream:
sequential 3363280744568882000000,000000 (5083900 ns)
parallel 3363280744568882500000,000000 (4492600 ns)
klein 3363280744568882000000,000000 (1051600 ns)
kleinStream 3363280744568882000000,000000 (3277500 ns)
Unfortunately I did not correctly cause floating point errors, and its for me late.
Using a Stream instead of the kleinSum would need a reduction with at least 2 doubles (sum and correction), so a double[2] or in newer Java a Record(double sum, double cs, double ccs) value.
A far less magical auxiliary approach is to sort the input by magnitude.
float (used for readability reasons only, double has a precision limit too, used later) has a 24-bit mantissa (of which 23 bits are stored, and the 24th one is considered 1 for "normal" numbers), so if you have the number 2^24, you simply can't add 1 to it, the smallest increment it has is 2:
float f=1<<24;
System.out.println(Float.valueOf(f).intValue());
f++;
f++;
System.out.println(Float.valueOf(f).intValue());
f+=2;
System.out.println(Float.valueOf(f).intValue());
will display
16777216
16777216 <-- 16777216+1+1
16777218 <-- 16777216+2
while summing them in the other direction works
float f=0;
System.out.println(Float.valueOf(f).intValue());
f++;
f++;
System.out.println(Float.valueOf(f).intValue());
f+=2;
System.out.println(Float.valueOf(f).intValue());
f+=1<<24;
System.out.println(Float.valueOf(f).intValue());
produces
0
2
4
16777220 <-- 4+16777216
(of course the pair of f++s is intentional, 16777219 would not exist, just like 16777217 for the previous case. These are not incomprehensibly huge numbers, yet a simple line as System.out.println((int)(float)16777219); already prints 16777220).
The thing applies to double too, just there you have 53-bits precision.
Two things:
the documentation actually suggests this: API Note: Elements sorted by increasing absolute magnitude tend to yield more accurate results
sum() internally ends in Collectors.sumWithCompensation(), which explicitly writes that it's an implementation of Kahan summation. (GitHub link is of JetBrains because Java uses different source control, which is a bit harder to find and link - but the file is present in your JDK too, inside src.zip, usually located in the lib folder)
Ordering by magnitude is something like ordering by log(abs(x)), which is a bit uglier in code, but possible:
double t[]= {Math.pow(2, 53),1,-1,-Math.pow(2, 53),1};
System.out.println(DoubleStream.of(t).boxed().collect(Collectors.toList()));
t=DoubleStream.of(t).boxed()
.sorted((a,b)->(int)(Math.log(Math.abs(a))-Math.log(Math.abs(b))))
.mapToDouble(d->d)
.toArray();
System.out.println(DoubleStream.of(t).boxed().collect(Collectors.toList()));
will print an okay order
[9.007199254740992E15, 1.0, -1.0, -9.007199254740992E15, 1.0]
[1.0, -1.0, 1.0, 9.007199254740992E15, -9.007199254740992E15]
So it's nice, but you can actually break it with little effort (the first few lines show that 2^53 really is the "integer limit" for double, and also "reminds" us of the actual value, then the sum with a single +1 ends up being less than 2^53):
double d=Math.pow(2, 53);
System.out.println(Double.valueOf(d).longValue());
d++;
d++;
System.out.println(Double.valueOf(d).longValue());
d+=2;
System.out.println(Double.valueOf(d).longValue());
double array[]= {Math.pow(2, 53),1,1,1,1};
for(var i=0;i<5;i++) {
var copy=Arrays.copyOf(array, i+1);
d=DoubleStream.of(copy).sum();
System.out.println(i+": "+Double.valueOf(d).longValue());
}
produces
9007199254740992
9007199254740992 <-- 9007199254740992+1+1
9007199254740994 <-- 9007199254740992+2
0: 9007199254740992
1: 9007199254740991 <-- that would be 9007199254740992+1 with Kahan
2: 9007199254740994
3: 9007199254740996 <-- "rounding" upwards, just like with (float)16777219 earlier
4: 9007199254740996
TL;DR: you don't need your own Kahan implementation, but use computers with care in general.
I'm supposed to calculate
using Simpson's rule, with 4 sub intervals.
I surely do not want do it by hand so I have tried to write that algorithm in Java.
The formula for Simpson's rule is
And here is my code:
import java.util.Scanner;
import java.util.Locale;
public class Simpson {
public static void main(String[] args) {
Scanner input = new Scanner(System.in).useLocale(Locale.US);
//e= 2.718281828459045 to copy paste
System.out.println("Interval a: ");
double aInt = input.nextDouble();
System.out.println("Interval b: ");
double bInt = input.nextDouble();
System.out.println("How many sub intervals: ");
double teilInt = input.nextDouble();
double intervaldistance = (bInt-aInt)/teilInt;
System.out.println("h = "+"("+bInt+"-"+aInt+") / "+teilInt+ " = "+intervaldistance);
double total = 0;
System.out.println("");
double totalSum=0;
for(double i=0; i<teilInt; i++) {
bInt = aInt+intervaldistance;
printInterval(aInt, bInt);
total = prod1(aInt, bInt);
total = total*prod2(aInt, bInt);
aInt = bInt;
System.out.println(total);
totalSum=totalSum+total;
total=0;
}
System.out.println("");
System.out.println("Result: "+totalSum);
}
static double prod1(double a, double b) { // first product of simpson rule; (b-a) / 6
double res1 = (b-a)/6;
return res1;
}
static double prod2(double a, double b) { // second pproduct of simpson rule
double res2 = Math.log(a)+4*Math.log((a+b)/2)+Math.log(b);
return res2;
}
static void printInterval(double a, double b) {
System.out.println("");
System.out.println("["+a+"; "+b+"]");
}
}
Output for 4 sub intervals:
[1.0; 1.4295704571147612]
0.08130646125926948
[1.4295704571147612; 1.8591409142295223]
0.21241421690076787
[1.8591409142295223; 2.2887113713442835]
0.31257532785558795
[2.2887113713442835; 2.7182818284590446]
0.39368288949073565
Result: 0.9999788955063609
Now If I compare my solution with other online calculators (http://www.emathhelp.net/calculators/calculus-2/simpsons-rule-calculator/?f=ln+%28x%29&a=1&b=e&n=4&steps=on), it differs.. But I don't see why mine should be wrong.
My solution is 0.9999788955063609, online solution is 0.999707944567103
Maybe there is a mistake I made? But I have double checked everything and couldn't find.
You may be accumulating the rounding error by doing b_n = a_{n} + interval many times.
Instead you could be using an inductive approach, where you say a_n = a_0 + n*interval, since this only involves introducing a rounding error once.
I will test with actual numbers to confirm and flesh out the answer in a little bit, but in the meantime you can watch this explanation about accumulation of error from handmade hero
PS. As a bonus, you get to watch an excerpt from handmade hero!
UPDATE: I had a look at your link. While the problem I described above does apply, the difference in precision is small (you'll get the answer 0.9999788955063612 instead). The reason for the discrepancy in your case is that the formula used in your online calculator is a slightly different variant in terms of notation, which treats the interval [a,b] as 2h. In other words, your 4 intervals is equivalent to 8 intervals in their calculation.
If you put 8 rectangles in that webpage you'll get the same result as the (more accurate) number here:
Answer: 0.999978895506362.
See a better explanation of the notation used on that webpage here
I changed your delta calculation to the top to so that you don't calculate the delta over and over again. You were also not applying the right multipliers for the odd and even factors, as well as not applying the right formula for deltaX since it has to be: ((a-b)/n) /3
double deltaX = ((bInt-aInt)/teilInt)/3;
for(int i=0; i<=teilInt; i++) { //changed to <= to include the last interval
bInt = aInt+intervaldistance;
printInterval(aInt, bInt);
total = prod2(aInt, bInt, i+1, teilInt); //added the current interval and n. The interval is +1 to work well with the even and odds
totalSum += total;
aInt = bInt;
System.out.println(total);
}
System.out.println("");
System.out.println("Result: "+ (totalSum*deltaX)); //multiplication with deltaX is now here
To account for the right factor of f(x) i changed the prod2 to:
static double prod2(double a, double b, int interval, double n) {
int multiplier = 1;
if (interval > 0 && interval <= n){
//applying the right multiplier to f(x) given the current interval
multiplier = (interval % 2 == 0) ? 4 : 2;
}
return multiplier * Math.log(a);
}
Now it yields the correct result:
first of all i'd like to point out that i know this is a topic covered a lot but after about an hour of looking at other questions and trying what is suggested there i still can't get this to work.
I'm performing a binary search through Performances for numbers that are within 0.1 of a given number time. however, i need both the min/max targets and the number we are searching with (average) to be rounded to only one decimal place.
the method assumes that performance is sorted in ascending order.
public static int binarySearch(Performance[] performances, double time) {
if (performances == null){
return -1;
}
int first = 0;
int last = performances.length - 1;
double targetMax = time + 0.1;
double targetMin = time - 0.1;
targetMax = Math.round((targetMax * 100)) / 100.0;
targetMin = Math.round((targetMin * 100)) / 100.0;
while (first <= last){
int mid = (first + last)/2;
double average = performances[mid].averageTime();
average = Math.round((average * 100)) / 100.0;
if ((targetMax > average) && (targetMin < average) ){
return mid;
}
else if(average < targetMin){
last = mid -1;
}
else {
first = mid + 1;
}
}
return -1;
}
here's where it gets weird. the rounding i have done seems to work fine for targetMax and targetMin with 9.299999999 rounding to 9.3, however when rounding average down from 9.3333333333 it returns 9.33
i really am stumped on this one, aren't i doing the exact same thing to both variables?
new to this website so please excuse anything i've left out, just ask and ill edit it in. trying my best! :)
You're rounding both to two decimal places - it's just that 9.2999999 rounded is 9.30.
Change your 100s to 10 to round to a single decimal place in each case.
I am trying to iterate the value of I from 1 to 0 or from 0 to 1. But I have got some problem.
Please check the following codes:
double i = 1.0;
loop{ // Just use a loop to iterate the i. This is just a pseudocode.
// We can use while-loop or for-loop or timer.
// (I know there is no keyword "loop" in java)
i -=0.1;
if( i == 0.0){
// stop the loop
}
}
In the above code, the loop will never stop because when the variable i will become 0.7000000001 when i = 0.8 - 0.1 during the loop. i will have lots of decimal number when i = 0.1 - 0.1. so it will never equal to 0.0.
I apologized if my description of my question is not clear enough for you.
This may be a very easy question for pro programmers. But I cannot figure it out. Please let me know what I have done wrong.
I would recommend BigDecimal usage.
I know BigDecimal is used in financial systems, and not Double or Float, to describe exact numbers with decimal dots (i.e - prices).
Read more here
Why don't you like to iterate from 1...10 and divide current value by 10?
Something like this:
for(int i = 0; i <= 10; i++) {
double value = (double) i / 10d;
}
Or if you don't void to have precision issues you can use BigDecimal:
BigDecimal value = BigDecimal.ZERO;
for(int i = 0; i <= 10; i++) {
value = value.add(BigDecimal.valueOf(0.1d));
double doubleVal = value.doubleValue();
}
Use integers for iteration and scale them appropriately.
i = 10;
while (i != 0)
{
double d = i / 10.0;
// do stuff with d
i--;
}
This will work even if the scale factor is not representable in decimal.
Using BigDecimal will only work with decimal fractions. If for example you want to iterate by steps of one third it won't work.
It looks like BigDecimal.setScale truncates to the scale+1 decimal position and then rounds based on that decimal only.
Is this normal or there is a clean way to apply the rounding mode to every single decimal?
This outputs: 0.0697
(this is NOT the rounding mode they taught me at school)
double d = 0.06974999999;
BigDecimal bd = BigDecimal.valueOf(d);
bd = bd.setScale(4, RoundingMode.HALF_UP);
System.out.println(bd);
This outputs: 0.0698
(this is the rounding mode they taught me at school)
double d = 0.0697444445;
BigDecimal bd = BigDecimal.valueOf(d);
int scale = bd.scale();
while (4 < scale) {
bd = bd.setScale(--scale, RoundingMode.HALF_UP);
}
System.out.println(bd);
EDITED
After reading some answers, I realized I messed everything up. I was a bit frustrated when I wrote my question.
So, I'm going to rewrite the question cause, even though the answers helped me a lot, I still need some advice.
The problem is:
I need to round 0.06974999999 to 0.0698, that's because I know those many decimals in fact are meant to be 0.6975 (A rounding error in a place not under my control).
So i've been playing around with a kind of "double rounding" which performs the rounding in two steps: first round to some higher precision, then round to the precision needed.
(Here is where I messed up because I thought a loop for every decimal place would be safer).
The thing is that I don't know which higher precision to round to in the first step (I'm using the number of decimals-1). Also I don't know if I could find some unexpected results for other cases.
Here is the first way I discarded in favour of the one with the loop, which now looks a lot better after reading your answers:
public static BigDecimal getBigDecimal(double value, int decimals) {
BigDecimal bd = BigDecimal.valueOf(value);
int scale = bd.scale();
if (scale - decimals > 1) {
bd = bd.setScale(scale - 1, RoundingMode.HALF_UP);
}
return bd.setScale(decimals, roundingMode.HALF_UP);
}
These prints the following results:
0.0697444445 = 0.0697
0.0697499994 = 0.0697
0.0697499995 = 0.0698
0.0697499999 = 0.0698
0.0697444445 = 0.069744445 // rounded to 9 decimals
0.0697499999 = 0.069750000 // rounded to 9 decimals
0.069749 = 0.0698
The questions now are if there is a better way to do this (maybe a different rounding mode)? and if this is safe to use as a general rounding method?
I need to round many values and having to choose at runtime between this and the standard aproach depending on the kind of numbers I receive seems to be really complex.
Thanks again for your time.
When you are rounding you look at the value that comes after the last digit you are rounding to, in your first example you are rounding 0.06974999999 to 4 decimal places. So you have 0.0697 then 4999999 (or essentially 697.4999999). As the rounding mode is HALF_UP, 0.499999 is less than 0.5, therefore it is rounded down.
If the difference between 0.06974999999 and 0.06975 matters so much, you should have switched to BigDecimals a bit sooner. At the very least, if performance is so important, figure out some way to use longs and ints. Double's and floats are not for people who can tell the difference between 1.0 and 0.999999999999999. When you use them, information gets lost and there's no certain way to recover it.
(This information can seen insignificant, to put it mildly, but if travelling 1,000,000 yards puts you at the top of a cliff, travelling 1,000,001 yards will put you a yard past the top of a cliff. That one last yard matters. And if you loose 1 penny in a billion dollars, you'll be in even worse trouble when the accountants get after you.)
If you need to bias your rounding you can add a small factor.
e.g. to round up to 6 decimal places.
double d =
double rounded = (long) (d * 1000000 + 0.5) / 1e6;
to add a small factor you need to decide how much extra you want to give. e.g.
double d =
double rounded = (long) (d * 1000000 + 0.50000001) / 1e6;
e.g.
public static void main(String... args) throws IOException {
double d1 = 0.0697499994;
double r1 = roundTo4places(d1);
double d2 = 0.0697499995;
double r2= roundTo4places(d2);
System.out.println(d1 + " => " + r1);
System.out.println(d2 + " => " + r2);
}
public static double roundTo4places(double d) {
return (long) (d * 10000 + 0.500005) / 1e4;
}
prints
0.0697499994 => 0.0697
0.0697499995 => 0.0698
The first one is correct.
0.44444444 ... 44445 rounded as an integer is 0.0
only 0.500000000 ... 000 or more is rounded up to 1.0
There is no rounding mode which will round 0.4 down and 0.45 up.
If you think about it, you want an equal chance that a random number will be rounded up or down. If you sum a large enough number of random numbers, the error created by rounding cancels out.
The half up round is the same as
long n = (long) (d + 0.5);
Your suggested rounding is
long n = (long) (d + 5.0/9);
Random r = new Random(0);
int count = 10000000;
// round using half up.
long total = 0, total2 = 0;
for (int i = 0; i < count; i++) {
double d = r.nextDouble();
int rounded = (int) (d + 0.5);
total += rounded;
BigDecimal bd = BigDecimal.valueOf(d);
int scale = bd.scale();
while (0 < scale) {
bd = bd.setScale(--scale, RoundingMode.HALF_UP);
}
int rounded2 = bd.intValue();
total2 += rounded2;
}
System.out.printf("The expected total of %,d rounded random values is %,d,%n\tthe actual total was %,d, using the biased rounding %,d%n",
count, count / 2, total, total2);
prints
The expected total of 10,000,000 rounded random values is 5,000,000,
the actual total was 4,999,646, using the biased rounding 5,555,106
http://en.wikipedia.org/wiki/Rounding#Round_half_up
What about trying previous and next values to see if they reduce the scale?
public static BigDecimal getBigDecimal(double value) {
BigDecimal bd = BigDecimal.valueOf(value);
BigDecimal next = BigDecimal.valueOf(Math.nextAfter(value, Double.POSITIVE_INFINITY));
if (next.scale() < bd.scale()) {
return next;
}
next = BigDecimal.valueOf(Math.nextAfter(value, Double.NEGATIVE_INFINITY));
if (next.scale() < bd.scale()) {
return next;
}
return bd;
}
The resulting BigDecimal can then be rounded to the scale needed.(I can't tell the performance impact of this for a large number of values)