-2

Having the code

public static final float epsilon = 0.00000001f;

public static final float a [] = {
        -180.0f,
        -180.0f + epsilon * 2,
        -epsilon * 2
}

The a is initialized as follows:

[-180.0, -180.0, -2.0E-8]

Instead of desired

[-180.0, X, Y]

How to tune epsilon to achieve the desired result? --


1) I want float rather than double to be coherent with the previously written code
2) I do not want -179.99999998 or any other particular number for X, I want X > -180.0 but X as much as possible close to -180.0
3) I want Y to be as much as possible close to 0, but to be it float
4) I want -180.0 < X < Y

In my initial post I have not specified precisely what I want. Patricia Shanahan guessed that by suggesting Math.ulp

Antonio
  • 756
  • 7
  • 26

3 Answers3

3

As recommended in prior answers, the best solution is to use double. However, if you want to work with float, you need to take into account its available precision in the region of interest. This program replaces your literal epsilon with the value associated with the least significant bit of 180f:

import java.util.Arrays;

public class Test {
  public static final float epsilon = Math.ulp(-180f);

  public static final float a [] = {
          -180.0f,
          -180.0f + epsilon * 2,
          -epsilon * 2
  };

  public static void main(String[] args) {
    System.out.println(Arrays.toString(a));
  }

}

Output:

[-180.0, -179.99997, -3.0517578E-5]
Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
  • Yes, I need to use `float` for now, not `double`. I just tried the "best" `epsilon` that come to my mind. The hint with `Math.ulp(-180f)` is especially helpful. As I commented my answer, I suppose that I loose precision much earlier during computations than seems for the first time. – Antonio Jun 23 '15 at 17:24
  • 3
    @Antonio Using float, be very, very careful about the numerical stability of your calculations. With such limited precision, it is not easy to be sure any digits of the final result are meaningful. – Patricia Shanahan Jun 23 '15 at 17:55
0

Although the value 0.00000001f is within the float's precision capacity, the value -180f + 0.00000001f * 2 (-179.99999998) is not. float has only about 7-8 significant digits of precision, and -179.99999998 requires at least 11. So the least significant bits of it get dropped by the addition operation, and the imprecise value ends up being -180.0f.

Just for the fun of it, here are those values in bits (n = -180.0f):

           sign
           | exponent       significand
           - -------- -----------------------
epsilon  = 0 01100100 01010111100110001110111
epsilon2 = 0 01100101 01010111100110001110111
n        = 1 10000110 01101000000000000000000
result   = 1 10000110 01101000000000000000000

The result ends up being bit-for-bit the same as the original -180.0f.

If you use double, that problem goes away, because you aren't exceeding double's ~15 digits of precision.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • As I commented earlier, Java can't drop digits from `-179.99999998`. We loose precision much earlier. We would get `-179.99999` what will lead to a huge precision loss since `-180` is much closer to `-179.99999998` than to `-179.99999`. – Antonio Jun 23 '15 at 17:26
  • @Antonio: You're thinking in decimal. `float` thinks in binary, remember it's a [IEEE-754 single-precision binary floating point number](https://en.wikipedia.org/wiki/Single-precision_floating-point_format). What gets dropped are the least significant **bits**. They don't get dropped earlier, because both `-180.0f` and `2.0E-8f` *can* be held in the 23 bits of significand; the problem comes when you add them together. You overflow the available significand bits, and the least significant ones are dropped. – T.J. Crowder Jun 23 '15 at 17:31
  • Crowdler Yes, but we must drop them before we get `-179.99999998`. – Antonio Jun 23 '15 at 17:42
  • 1
    @Antonio: Well, it's basically "en route to" getting that value; gory details in [JLS§15.18.2](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.18.2), but basically it says that *"...the exact mathematical sum is computed..."* and then *"... the sum is rounded to the nearest value in the chosen value set using IEEE 754 round-to-nearest mode..."*. So a literal read of the spec says at some point, there's a `-179.99999998` kicking around somewhere. :-) (But I bet implementations can optimize, as long as they can prove the optimization isn't observable.) – T.J. Crowder Jun 23 '15 at 17:55
  • Especially interesting thing is that "the exact mathematical sum is computed". Maybe this is specific only to Java according to JLS? – Antonio Jun 23 '15 at 20:01
  • @Antonio I think this has to be interpreted as meaning the result is the same as if the exact mathematical sum had been computed and then rounded. – Patricia Shanahan Jun 23 '15 at 20:40
  • @Antonio: ^^ What Patricia said. Again, I'm sure implementations can optimize, as long as the optimization isn't observable in the result. – T.J. Crowder Jun 24 '15 at 06:39
  • There are [rules](https://www.doc.ic.ac.uk/~eedwards/compsys/float/) for arithmetic operations with floating point numbers. It tells how parts (Mantissa and Exponent) should be modified to calculate the result. However, different compilers may use other faster algorithms as well as optimizations. – Antonio Jun 24 '15 at 09:28
  • @Antonio: Yes, there are. What's your point? Neither Patricia nor I ever said there weren't...? – T.J. Crowder Jun 24 '15 at 09:30
  • By the way, there is a [on-line converter](http://www.h-schmidt.net/FloatConverter/IEEE754.html) between decimal and binary representations. – Antonio Jun 24 '15 at 09:33
  • @Antonio: Yes, that would be the `String s = Integer.toBinaryString(Float.floatToRawIntBits(value));` in my linked code. The rest is outputting for clarity. – T.J. Crowder Jun 24 '15 at 09:40
  • Sure, there may be optimizations. We _may_ drop the precision earlier before we get `-179.99999998` due to optimizations. However, according to the rule _"...the exact mathematical sum is computed..."_ we _must_ think of `-179.99999998` and then round it according to JLS. This rule gives more clear understanding of how Java arithmetic actually works and what to expect from it. For [multiplication](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.17) we have the same. – Antonio Jun 24 '15 at 10:02
  • @Antonio: Yes. That's what I said. I don't think there's any need for a further conversation about this. – T.J. Crowder Jun 24 '15 at 10:04
-2

Try to "double" key. If it is not enough for you, try "long double".

stuck
  • 1,477
  • 2
  • 14
  • 30
  • I answered with C logic, you are right about that but the code above is not complex at all; Java is that brilliant, my friend :) By the way thanks about the warning. @PascalCuoq – stuck Jun 23 '15 at 16:23