ryu icon indicating copy to clipboard operation
ryu copied to clipboard

Java: in correct rounding to two digits

Open ulfjack opened this issue 6 years ago • 6 comments

The Java implementation sometimes doesn't output the closest two-digit representation of a number, but instead rounds to one digit (correctly), and then appends a '0'.

Known examples: 1.0E-323 should be 9.9E-324 1.0E-322 should be 9.9E-323

ulfjack avatar Dec 27 '18 19:12 ulfjack

@ulfjack

I just tried to replicate this issue as part of my efforts to port RYU to Scala Native. Please pardon any lack of understanding or stupidity on my part, When I use https://repl.it/languages/java to run the java code

class Main {
  public static void main(String[] args) {
    System.out.println(Double.toString(1.0E-323));
    System.out.println(Double.toString(9.9E-324));
  }
}

I get the same output 1.0E-323 for both 1.0E-323 and 9.9E-324. That is the output reported as incorrect/buggy in this issue. Seems like Java did not get the memo.

Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
1.0E-323
1.0E-323

Using the web site avoids an peculiarities on my local machine but uses an old version of Java 8.

I get the same results when I run the same code on my local 64 bit machine running 'openjdk 11.0.1 2018-10-16'

Does RYU truely have a bug here or am I not seeing something, either subtle or obvious?

Thank you,

Lee

PS: The rest of the RYU port to Scala Native seems to be running just fine. Thank you for RYU.

LeeTibbert avatar Feb 25 '19 15:02 LeeTibbert

Well, whether the current output is correct or not depends on what exactly the specification for Double.toString says. Unfortunately, it's not very precise in a mathematical sense, and this is a rather esoteric corner case. The spec basically says that we should print (a) the shortest number, with (b) at least two digits, that (c) parses back to the original number and (d) is closest to the exact value.

Ignoring (b), the shortest number would clearly be 1e-323. The crux of the matter is the requirement (b).

Should we compute the shortest number, and then print two digits? Then it should be 1.0e-323.

Should we compute the two-digit number that is closest to the original input? Then it should be 9.9e-324.

Clearly, we have to pick one of these interpretations. In my opinion, the second interpretation is slightly preferable, that is, I prefer the printed number to be slightly closer to the exact value. We have two digits, so why not use them? There was a brief discussion of this on one of the OpenJDK mailing lists, and there was no strong argument against this interpretation.

And yes, that implies that the current OpenJDK implementation of Double.toString is incorrect. There are actually more cases where it is incorrect, and more clearly so - there are some cases where it prints too many digits (which is certainly better than too few!).

ulfjack avatar Feb 27 '19 19:02 ulfjack

@ulfjack Thank you for the additional description.

Java does not have as long a history as C but it still has 25 or so years of use "in the wild". To some/most, "long standing existing practice" is a useful decision rule for situations where any (probably post-hoc) specification is unclear. I did not go all the way back to the earliest Java release, but I suspect that it the round trip of 1e-323 would yield 1.0E-323.

I suspect that I an not the only developer who would evaluate Ryu for correctness by running (many) test cases of "matched pairs" comparing Ryu and Java output. I would find Ryu printing out 9.9E-324 somewhere between mildly astounding and regrettable.

I would have to implement a workaround to avoid spending the rest of my professional life explaining the difference to the more sharp-eyed of my user base.

With full support for your efforts, I say "Your field, your rules" but urge you to consider what I suspect is long standing Java practice.

LeeTibbert avatar Feb 28 '19 11:02 LeeTibbert

Some say that Java is defined by its abundant implementation "quirks".
Many have grumbled that any attempt to match Java results by a rational means or specification is doomed. I for one have plenty of scars on my hide from Java quirks.

If the goal is to have Ryu follow a spec more closely (a laudable goal), perhaps a solution would be to paste on a big label saying "RyuDouble.java is not Java", similar to the wordplay beloved in some circles "Xinu is not unix" and such? Perhaps that is what you are saying already and did not twig to it?

I am probably going to have to take the "Java-like, not one-to-one Java" approach myself.

LeeTibbert avatar Feb 28 '19 11:02 LeeTibbert

The discussion thread in upstream is about merging a new implementation that returns 9.9E-324 in this case, as well as a more mathematically precise specification. I tested Ryu against the new implementation and discovered that Ryu returns 1.0E-323 in this case (but otherwise found no differences in output). I don't know if or when upstream will merge a new implementation and whether it will be the one that was discussed.

In any case, I can clarify in the README that the current behavior differs from Double.toString in some cases.

ulfjack avatar Feb 28 '19 21:02 ulfjack

There aren't a lot of one-digit and two-digit numbers across all possible double values, so I could check all of them against OpenJDK.

ulfjack avatar Feb 28 '19 21:02 ulfjack