2

So I'm more or less a noob when it comes to coding. I'm learning java for a university course, and one of my exercises requires me to write a code that evaluates an integral using the left endpoint method, midpoint method and the trapezium method.

For this I need to use a for loop that increments by one step each time. I know how to do this just using double, but the problem is, when I'm running calculations using the double data type, it gives me rounding errors. After reading up (mostly looking at other stack overflow questions), I figured I should use BigDecimal to remove the rounding error.

However using BigDecimal gives me a whole bunch of errors. From what I can tell, I can't use normal operators such as <, >, == etc, because BigDecimal is not a primitive data type. But then I don't know how to do a basic for loop that increments whilst still using BigDecimal. Another thing I looked at here suggested using Commons-math? (second answer down)

https://stackoverflow.com/questions/16707397/whats-wrong-with-this-simple-double-calculation#=

I don't have the faintest idea how to use this as, as far as I can tell, it's not similar to just plain old import java.lang.Math for example.

So I guess my question is, how can I get my code to work using BigDecimal in a for loop, and in comparing numbers together generally, or how can I use Common-Math to simply avoid the rounding error altogether?

Here is my code:

import java.util.Scanner;
import java.lang.Math;
import java.math.BigDecimal;

public class IntegrationMethods{

  //calculates the value of the function f(x)=x^2
  public static BigDecimal f(BigDecimal x){
    return x.multiply(x);
  }

  //uses a, b and N as arguments, calculates sum of integral using left 
endpoint rule
  public static BigDecimal leftEndpoint(BigDecimal a, BigDecimal b, 
BigDecimal n){
    BigDecimal h = (b.subtract(a)).divide(n);
    sum = new BigDecimal("0");
    i = new BigDecimal("0");
    for(i.compareTo(n)<=0;){
      sum = sum.add(h.multiply(f(a.add(h.multiply(i-1)))));
      i = i.add(new BigDecimal(1));
    }
    return sum;
  }

  public static BigDecimal midpoint(BigDecimal a, BigDecimal b, BigDecimal 
n){
    BigDecimal h = (b.subtract(a)).divide(n);
    BigDecimal sum = 0.0;
    for(BigDecimal i=0.0; i<n; i++){
      BigDecimal x1 = a.add(h.multiply(i));
      BigDecimal x2 = a.add(h.multiply(i+1));
      sum = sum.add(h.multiply((f(x1).add(f(x2))).divide(2)));
    }
    return sum;
  }

  public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);

System.out.println("Please enter the lower limit of integration a.");
BigDecimal a = scanner.nextBigDecimal();

System.out.println("Please enter the upper limit of integration b.");
BigDecimal b = scanner.nextBigDecimal();

//swaps a and b so that the bigger of the two is always b.
//this prevents a negative popping up later on.
if(b<a){
  BigDecimal r = a;
  BigDecimal m = b;
  a = m;
  b = r;
}

System.out.println("Please enter the number of sampling points N.");
BigDecimal n = scanner.nextBigDecimal();

//checks if n is greater than or equal to 1, and asks for a new value if it isn't.
while (n<1.0){
  System.out.println("Please enter a new value for N.");
  n = scanner.nextBigDecimal();
}

System.out.println("For " + n + " intervals, the integral of x^2 using the left endpoint rule is: " + leftEndpoint(a,b,n));
System.out.println("Using the midpoint rule, the integral of x^2 is: " + midpoint(a,b,n));
  }
}
  • Why are you trying to use a floating-point value as a loop counter anyway? – azurefrog Nov 08 '17 at 17:08
  • The trick is to read the javadoc of BigDecimal. https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html – JB Nizet Nov 08 '17 at 17:10
  • It's mostly because of the n I think. I use it in one of my calculations so it also has to be BigDecimal, then everything else in the for loop has to be BigDecimal as well otherwise I get a bad operand type error. – Lizi Hutchinson Nov 08 '17 at 17:22
  • BigDecimal is designed for calculations with money. For physical/mathematical quantities, just use double. Avoid rounding errors by adding up the smaller terms together before the larger terms. – DodgyCodeException Nov 08 '17 at 17:26
  • I'm not just adding though, I'm multiplying and squaring also, this is when I get the rounding error. I initially had everything in double, that was when I switched to BigDecimal, how would I avoid the rounding errors there? – Lizi Hutchinson Nov 08 '17 at 17:30

3 Answers3

10

A normal loop to count from 0 to 19 is:

for (int i = 0; i < 20; i++)
    System.out.println(i);

The same loop done using BigDecimal looks like this:

BigDecimal end = BigDecimal.valueOf(20);
for (BigDecimal i = BigDecimal.ZERO; i.compareTo(end) < 0; i = i.add(BigDecimal.ONE))
    System.out.println(i);

The way to use <, >, == etc with BigDecimal, is to use the compareTo() method. The javadoc shows how to do this:

This method is provided in preference to individual methods for each of the six boolean comparison operators (<, ==, >, >=, !=, <=). The suggested idiom for performing these comparisons is: (x.compareTo(y) <op> 0), where <op> is one of the six comparison operators.

So, i < end is written as i.compareTo(end) < 0.

Also notice that BigDecimal has predefined constants for 0, 1, and 10 (BigDecimal.ZERO, BigDecimal.ONE, and BigDecimal.TEN), and that creating BigDecimal integer values should use BigDecimal.valueOf(long val), rather than new BigDecimal(String val).

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • I understood the Javadoc, everyone seems to think I didn't or didn't read it, my main problem was then comparing the integer value that compareTo gives, to the BigDecimal value of n. – Lizi Hutchinson Nov 08 '17 at 17:27
  • @LiziHutchinson If you think you need to compare the `int` value returned from `compareTo` with the value of `n`, then you obviously *didn't* understand the javadoc. The javadoc explicitly shows you comparing the `int` value only with `0`. Read the javadoc again. The magnitude of the `int` value has no meaning. Only whether the value is `== 0`, `< 0`, or `> 0`. – Andreas Nov 08 '17 at 17:29
  • @LiziHutchinson This is an answer to *"I don't know **how to do a basic for loop** that increments whilst still using BigDecimal"* and *"*how can I get my code to work** using BigDecimal in a for loop, and in **comparing numbers together generally**"*. Isn't that what you're asking? – Andreas Nov 08 '17 at 17:38
  • Sorry, I've been editing my code, I see what you mean now. I still sometimes get a bit mixed up between types. If I wanted to divide a BigInteger by 2, could I do Int1.divide(new BigInteger int2= "2") or would I have to declare int2 serperately? – Lizi Hutchinson Nov 08 '17 at 17:47
  • @LiziHutchinson For `BigInteger`, not `BigDecimal`, division by 2, specifically, is better done using shift: `bi = bi.shiftRight(1)`. To divide by some integer value, use `bi = bi.divide(BigInteger.valueOf(intValue))`. If you divide by the same integer value a lot, e.g. in a loop, you should assign `BigInteger.valueOf(intValue)` to a variable outside such loop. – Andreas Nov 08 '17 at 18:04
  • Sorry, I did mean BigDecimal, got lost somewhere between the two and ended up getting even more errors in the code until I realised. I've compiled it all now though and I am getting no more errors and it's working as it should though, so thank you for your help! :) – Lizi Hutchinson Nov 08 '17 at 18:07
0

Each piece of a for loop can have any expression you desire. The first one initializes a variable, the second must evaluate to a boolean, and the third is any expression. So for example:

for (BigDecimal i = BigDecimal.ZERO; i.compareTo(n) < 0; i = i.add(BigDecimal.ONE)) {
}
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
-1

It is a bad idea to use a floating point value like BigDecimal as a loop counter as it can lead to unexpected behavior. You can read more about that here: https://www.securecoding.cert.org/confluence/display/java/NUM09-J.+Do+not+use+floating-point+variables+as+loop+counters

If you make your for-loop counter of type int, you can just wrap it in a new BigDecimal instance to pass to h.multiply() and yield the expected result:

for(int i=0; BigDecimal.of(i).compareTo(n) < 0; i++){
  BigDecimal x1 = a.add(h.multiply(BigDecimal.of(i)));
  BigDecimal x2 = a.add(h.multiply(BigDecimal.of(i+1)));
  sum = sum.add(h.multiply((f(x1).add(f(x2))).divide(2)));
}
Austin Hill
  • 119
  • 1
  • 7