tags:

views:

310

answers:

6

I wrote some Java code to generate pseudo-random numbers but I'm running into what I think are rounding problems getting it to match up with the same code in Ruby. Here's what I have in Java:

public class randTest {
  private final static Double A = Math.pow(5,13);
  private final static Integer S = 314159265;
  private final static Double minVal = Math.pow(2, -46);
  private final static Double maxVal = Math.pow(2, 46);
  private final static Double newMax = 10.0;

  private static Double r(Integer k) {
    Double powS = Math.pow(A, k) * S;
    Double xk = powS % maxVal.intValue();
    Double result = minVal * xk;
    System.out.println("k = " + k + ", pows = " + powS + ", xk = " + xk + ", result = " + result);
    return result;
  }
}

And the same thing in Ruby (my working reference implementation):

A = 5 ** 13
S = 314159265
MIN_VAL = 2 ** -46
MAX_VAL = 2 ** 46
NEW_MAX = 10

def r(k) # was generate_random
  powS = (A ** k) * S
  xk = powS % MAX_VAL
  result = MIN_VAL * xk
  puts "k = #{k}, pows = #{powS}, xk = #{xk}, result = #{result}"
  return result
end

For some reason, the output is vastly different (but the Ruby one is correct). Here's me calling r(4):

Java:

k = 4, pows = 6.9757369880463215E44, xk = 1.512592341E9, result = 2.1495230001278287E-5

Ruby:

k = 4, pows = 697573698804632158498861826956272125244140625, xk = 55279057169489, result = 0.785562650228954

Any idea as to why powS would be computed correctly in both but not xk? Note that on the Java version I needed to use maxVal.intValue() instead of maxVal, otherwise it returns zero. I've also tried replacing the Doubles with BigDecimals to no avail as well.

+2  A: 

My Java is rusty, but doesn't intValue() go to a 32 bit int value? 2^46 would be too large to fit in there.

Marc Hughes
Java has 32-bit signed ints, so yeah. It looks like Ruby uses 64-bit integers (corresponding to Java's `long` type); is that true?
Michael Myers
+2  A: 

When you call intValue() the double value will be cast to an int.

Java's Integer MAX_VALUE:

A constant holding the maximum value an int can have, 2^31-1.

Your MAX_VAL:

2 ** 46

Even the long type cannot hold values big enough. I think as @toolkit says, you need the BigInteger class from the java.math package.

Brabster
+1  A: 

697573698804632150000000000000000000000000000
and
697573698804632158498861826956272125244140625
are not the same number. You are losing precision in Java by having such a large number, where Ruby has arbitrary precision integer arithmetic. Applying arithmetic operations to numbers that large in Java will result in [pseudo-]unpredictable results.

Sparr
+2  A: 

Two things:

  1. In a situation like this, you'll need something like BigInteger to make sure the values don't overflow their data types.
  2. In a related issue, maxVal.intValue() returns 2147483647, far shy of the roughly 7.03E13 it should be.
Pesto
+3  A: 

You're getting truncation errors when you call maxVal.intValue()

Take a look at BigDecimal and BigInteger to achieve the same as your ruby snippet.

BTW: If you use groovy which sits on top of Java, then this uses BigDecimal out of the box.

Example code:

public class Rounding {
    private final static BigDecimal A = BigDecimal.valueOf(Math.pow(5, 13));
    private final static int S = 314159265;
    private final static BigDecimal minVal = BigDecimal.valueOf(Math
            .pow(2, -46));
    private final static BigDecimal maxVal = BigDecimal
            .valueOf(Math.pow(2, 46));
    private final static BigDecimal newMax = BigDecimal.valueOf(10);

    public static void main(final String[] args) {
        r(4);
    }

    private static void r(final int k) {
        final BigDecimal powS = A.pow(k).multiply(BigDecimal.valueOf(S));
        final BigDecimal xk = powS.remainder(new BigDecimal(maxVal
                .toBigInteger()));
        final BigDecimal result = minVal.multiply(xk);
        System.out.println("k = " + k + ", pows = " + powS + ", xk = " + xk
                + ", result = " + result);

    }
}

Produces:

k = 4, pows = 697573698804632158498861826956272125244140625, xk = 55279057169489, result = 0.785562650228953900455100455956
toolkit
+1  A: 

The problems is that Double can not hold the value you are trying to put into it, so it's getting truncated.

For large values like this, you need to use java.math.BigDecimal, which allows for an arbitrarily large precision for decimal values.

Here's you're Java sample redone using BigDecimal:

public class RandTest {

    private final static BigDecimal A = new BigDecimal(5).pow(13);
    private final static BigDecimal S = new BigDecimal(314159265);
    private final static BigDecimal minVal = 
         new BigDecimal(2).pow(-46, new MathContext(100));
    private final static BigDecimal maxVal = new BigDecimal(2).pow(46);
    private final static BigDecimal newMax = new BigDecimal(10.0);

    private static BigDecimal r(Integer k) {
     BigDecimal powS = A.pow(k).multiply(S);
     BigDecimal xk = powS.remainder(maxVal);
     BigDecimal result = minVal.multiply(xk);
     System.out.println("k = " + k + ", pows = " + powS + ", xk = " + xk
       + ", result = " + result);
     return result;
    }
}

This version property returns the correct result you're looking for.

BCunningham
As does the exactly identical code posted by toolkit 10 minutes ago.
Michael Myers
yep...comment didn't make it in fast enough :)
BCunningham
Upon further examination of tookit's code -- I'd argue that the version i posted is actually the correct one: He's still using Math.pow() in the assignments to BigDecimal, which returns a double and hence still risks losing precision. In this case it seems to work fine, but I'm not sure if this is correctly defined behavior, or just a quirk of the VM.
BCunningham
Yes, I also like the use of pow in BigDecimal as opposed to in double. Thanks for the help, I appreciate it a lot!
Chris Bunch