0

I have a simple timestamp based expression which extracts value from JSONObject. The following is an excerpt of the code,

Long cur_trig = Long.parseLong(api_content.get("timestamp").toString());
Long last_trig  = Long.parseLong(trigger_info.get(key)); 
frequency = 60.0
if ( last_trig + frequency*1000 > cur_trig) {  
  System.out.println("Too early for an alert");
}

The above code will not satisfy the if condition, even if it's theoretically true.

Long curr_trig = 1455213601000L;
Long last_trig = 1455213600000L;
double freq = 60.0;
if (last_trig + freq * 1000 > curr_trig) {
    System.out.println("Yup its correct");
}

In this case, it works perfectly fine. I have manually printed the values in the first case and the numbers do add up. The weird thing, I noticed that, if I manually typecast the values:

Long next_trig_time  = (long) (last_trig + alert.frequency*1000); 
if ( next_trig_time > cur_trig) { 
    System.out.println( "Its fine!");
}

This seems to work fine. What I fail to understand is, should Java automatically typecast the values to higher data type while evaluating an expression? Or atleast throw a warning / error?

Edit 1: Based on suggestions, I added the following

boolean notified = last_trig + alert.frequency*1000 > cur_trig ;
if (notified) { 
    System.out.println( "Its fine!");
}

This doesn't make any difference compared to the first case.

Edit 2: Using long instead of Long:

long last_trig  = Long.parseLong(trigger_info.get(key));
long cur_trig = Long.parseLong(api_content.get("timestamp").toString());
frequency = 60.0
boolean notified = last_trig + frequency*1000 > cur_trig ;
if (notified) { 
   System.out.println( "Its fine!");
}

Surprisingly, even this ain't working.

Edit 3: Here is a reproducible issue

JSONObject js = new JSONObject();
js.put("last_trig","1455213601000");
js.put("cur_trig", "1455213600000");

Long last_trig = Long.parseLong(js.get("last_trig").toString());
Long cur_trig = Long.parseLong(js.get("cur_trig").toString());
float frequency = (float) 60.0;
Long next_trig_time = (long) (last_trig + frequency*1000);

if (last_trig + frequency*1000 > cur_trig) {  
    System.out.println("[Case 1] Too early for an alert");
}
else {
    System.out.println("[Case 1] Too late for an alert");
}

if ( next_trig_time > cur_trig) {  
    System.out.println("[Case 2] Too early for an alert");
}
else {
    System.out.println("[Case 2] Too late for an alert");
}

Current output:

[Case 1] Too late for an alert
[Case 2] Too early for an alert

Expected output:

[Case 1] Too early for an alert
[Case 2] Too early for an alert

Tom
  • 16,842
  • 17
  • 45
  • 54
Rahul
  • 11,129
  • 17
  • 63
  • 76
  • 2
    Do you mean to use `long` instead of `Long` or are you expecting these values could be `null`? – Peter Lawrey Feb 23 '16 at 18:44
  • 2
    I suggest you look at all the values in your debugger or using println. Most likely, the values are not what you think they are. The numbers don't change the way they work based on their source. – Peter Lawrey Feb 23 '16 at 18:46
  • @PeterLawrey I have extensively logged all the events and the numbers do add up. I can share relevant log data to prove it. – Rahul Feb 23 '16 at 18:54
  • In that case, the comparison should be fine. I suggest you put the comparison in a `boolean` and print it as well. – Peter Lawrey Feb 23 '16 at 18:57
  • @PeterLawrey it doenst make any difference using boolean. I have editing the question to add the code snippet. – Rahul Feb 23 '16 at 19:17
  • 1
    It's time for a [mcve] which also contains the mentioned JSON. – Tom Feb 23 '16 at 19:32
  • @Tom I have updated the code. – Rahul Feb 23 '16 at 23:50
  • @Rahul using a boolean will help you debug whether the condition is really true/false as you believe it is. – Peter Lawrey Feb 24 '16 at 08:39
  • 1
    @Rahul of course you shouldn't be using `float` as this only have 6 digits of precision, just use `long` or `double`. You shouldn't be using `Long` either. – Peter Lawrey Feb 24 '16 at 08:40
  • 1
    @PeterLawrey thanks for the info. I had a nice refresher course in floating point arithemetic. Btw, this link http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html is helpful for anyone , who wants to know more about this stuff. – Rahul Feb 24 '16 at 18:50

2 Answers2

1

Short answer: float(s) in java do not have enough precision to do what you want. Basically, 6-7 significant digits is the max.

Workaround, use doubles.

    Long last_trig = Long.parseLong(js.get("last_trig").toString());
    Long cur_trig = Long.parseLong(js.get("cur_trig").toString());
    double frequency = 60.0;
    double v = last_trig + frequency * 1000;

    if (v > cur_trig) {
        System.out.println("[Case 1] Too early for an alert");
    }
    else {
        System.out.println("[Case 1] Too late for an alert");
    }

    Long next_trig_time = (long) v;
    if ( next_trig_time > cur_trig) {
        System.out.println("[Case 2] Too early for an alert");
    }
    else {
        System.out.println("[Case 2] Too late for an alert");
    }

[Case 1] Too early for an alert [Case 2] Too early for an alert

Fun stuff:

    long l = 0x123456712345678L;
    float d = l;
    long lAgain = (long) d;
    System.out.println(lAgain == l);

Guess what it prints ?

Nicolas Modrzyk
  • 13,961
  • 2
  • 36
  • 40
0

Let's compare two code snippets:

You said that this works...:

Long curr_trig = 1455213601000L;
Long last_trig = 1455213600000L;
double freq = 60.0;
if (last_trig + freq * 1000 > curr_trig) {
    System.out.println("Yup its correct");
}

...and that this fails:

Long last_trig = Long.parseLong(js.get("last_trig").toString());
Long cur_trig = Long.parseLong(js.get("cur_trig").toString());
float frequency = (float) 60.0;
if (last_trig + frequency*1000 > cur_trig) {  
    System.out.println("[Case 1] Too early for an alert");
}

The most important difference here is the type of frequency. It influences the type of last_trig + frequency*1000 and float doesn't seem to be able to handle your numbers correctly. It will treat the result as 1.45521364E12 (I guess it isn't 1.45521366E12 due to flating point inaccuracies). The right hand side might also be "promoted" to float in order to compare them (reference) and (float)cur_trig is also 1.45521366E12.

You can fix that by either:

  • changing frequency to double:

    double frequency = 60.0;
    
  • casting to double:

    if (last_trig + (double)(frequency * 1000) > cur_trig) {
    
  • allowing equal values (so the inaccurate float will be supported):

    if (last_trig + frequency * 1000 >= cur_trig) {
    
Community
  • 1
  • 1
Tom
  • 16,842
  • 17
  • 45
  • 54
  • I figured that out. But I still dont understand, why does java behave this way ? Shouldnt ideally it should cast to higher data type. or atleast throw some error / warning ? – Rahul Feb 24 '16 at 00:41
  • Java doesn't warn you about ongoing overflows (and it doesn't throws exceptions). It will just treat `last_trig + (frequency * 1000)` as `float` which will then be `1.45521364E12` (due to floating point inaccuracies) and presumably also `cur_trig` as `float` which will also be `1.45521364E12`. So, using `if (last_trig + (frequency * 1000) >= cur_trig)` would also work. – Tom Feb 24 '16 at 00:53
  • 1
    @Rahul There is no such thing as a "higher data type" in this case: `double` can be 3.5, which `long` can't; and `long` can be 2^63-1, which `double` can't. About warnings, usually loss of precision when converting to `float` or `double` is not warned as it would be too annoying. Notice that even a simple value as 0.1 (decimal) can't be represented exactly by either `float` or `double`. – Jojonete Feb 24 '16 at 13:10