Skip to main content

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

  • From: Jitendra Kotamraju < >
  • To:
  • Cc: Joe Darcy < >
  • Subject: [json-processing-spec users] Re: BigDecimal for equals()/hashCode()
  • Date: Thu, 24 Jan 2013 12:12:55 -0800

On 01/24/2013 08:13 AM, Jonathan Fuerth wrote:
That's an entertaining response from Joe, but it's not a response to anything 
that has been suggested by either Martin or myself in the conversation so 
far. We all came into this understanding that floating point numbers are 
increasingly widely spaced as their magnitude increases.

To put a finer point on it:

1. Every Java byte, short, char, int, and float value has an exact double 
representation
2. The larger magnitude long values do not have an exact double representation
3. There are an effectively infinite number of BigInteger and BigDecimal 
values that have no double representation
4. Every BigDecimal value can be exactly represented by a String, although in 
some cases it might take more memory to do so

I take it as a given that everybody involved in this conversation understood 
all of the above before we got started, but I guess it helps to articulate 
the points for the sake of clarity.

Getting back to the discussion about JsonNumber, here's the context I was 
starting with:

1. The JSON data format is most commonly used to interchange data with web 
browsers, where the overwhelmingly common programming environment is 
JavaScript
2. All numbers in JavaScript are double-precision floating point. JSON data 
that flows through a JavaScript processing step will have all of its numbers 
rounded to a nearby double-precision floating point value. I don't think we 
can even assume a particular rounding rule (down, up, nearest, other?!).
3. The designer of an application that produces or consumes JSON data must 
assume that all numbers in the data structure might be shoe-horned into 
doubles, with the attending loss of precision, *unless* they know for certain 
that the data structure will never be processed in a web browser. (And how 
limiting of an assumption is that, over the lifetime of a system first 
designed in 2013?)

In practice, because of the above concerns and the ubiquity of web browsers, 
exact numeric values exchanged in JSON data structures (for example, unique 
IDs and monetary amounts) are usually transmitted as quoted strings rather 
than as numbers.

But even given the inherent dangers and limitations of trusting that a number 
you put into a JsonNumber will retain its precision at every stage of 
processing, nobody was suggesting that we just throw up our hands and let 
JSR353's JsonNumber play fast and loose with numeric precision. We're merely 
asking that common expectations and common-case performance constraints be 
accounted for.

My suggestion was simply that we should be careful in the specification of 
JsonNumber on these points:

1. JsonNumber should explicitly specify that it preserves the full precision 
of the value it was constructed from (this is potentially relevant for any 
JsonNumber constructed from a
We clearly specify how long, double, int, BigInteger, String -> BigDecimal conversion in javadoc. The semantics of BigDecimal are used for all other purposes. What else do you want me to specify in javadoc ? I can do that if there is better way to document it.
long, BigInteger, BigDecimal, or String value, but especially delicate on data paths like 
String -> JsonNumber -> BigDecimal or BigDecimal -> JsonNumber -> String 
where trailing zeroes are significant)

2. JsonNumber should be specified so that the implementation has the option 
to store the value as a String internally. This makes it cheap in both time 
and space to pass values through verbatim from one system to another. The 
overwhelming majority of JsonNumber instances will either begin their life as 
a String (parsed from an input stream) or end their lives as a String 
(serialized to an output stream). Additionally, there are plenty of use cases 
where the number will be read from an input stream, shuffled around (say, as 
a member of a JsonObject or JsonArray) and then written back out without ever 
being asked its numeric value.
3. JsonNumber's equals() and hashCode() methods should be specified in a 
manner compatible with an internal String representation (the tricky part 
here, as raised by Jitu in the original thread, is that we'd need to account 
for the rare case that a number uses exponential notation) so a JsonNumber 
implementation can correctly compare itself to another JsonNumber without 
creating additional objects
4. Notwithstanding:

   a. the promise in point 1 to *retain* precision and scale in JsonNumber; 
and
   b. the common-case performance concerns in points 2 and 3 about tying 
JsonNumber to BigDecimal,

   I submit that it goes against all reasonable expectations that a JsonNumber received on the wire as 
"10" would not compare equal to a JsonNumber received on the wire as "10.0" or 
"10.00". Leading and trailing zeroes that don't contribute to the number's magnitude should be 
disregarded for the purposes of equality. I think specifying otherwise in a
Should you want this to be limited to equality/hashCode or want to use for toString represenation ? Say JsonNumber num1 is constructed with double 10.0d, JsonNumber num2 is constructed with integer 10, do you want num1 and num2 to be equal ? What about representation, are you ok with both being written on the wire as 10 ? If that's the case,
then your suggestion would be as follows:
long, double, int, BigInteger, String -> BigDecimal -> BigDecimal#stripTrailingZeros() and use the last BigDecimal in the chain for toString(), equals(), hashCode(), getNumberType()

The other option you are suggesting is:
int, long, double, BigInteger, BigDecimal, String -> String and use for toString(), equals(), hashCode(), getNumberType(), getXXValue() methods etc. To get that right is not easy while taking care of exponential notation, and conversions. If you have the algorithms(or sample JsonNumber impl) for that, it would be easy to understand what cases would work and what cases wouldn't.

Java-JavaScript communication library constitutes a hidden trap.

So now that we're all (hopefully!) discussing the same topic, I'm keen to 
hear your insights.

-Jonathan

On 2013-01-24, at 12:07 AM, Jitendra Kotamraju wrote:

Martin proposed different algorithm for JsonNumber equals()/hashCode() in his 
review [1]. I am including Joe's response on this.

I am also ccing him, please include him in replies as he is not on the users 
mailing list. His response is as follows:
-----
In general, it is essentially unpredictable from inspection which decimal 
strings are exactly representable as double floating-point value.  Yes, there 
are many cases which are easy, but quick, pick which one of these two values 
is exactly representable as double:

4.940656458412465441765687928682213723650598026143247644255856825006755
0727020875186529983636163599237979656469544571773092665671035593979639
8774796010781878126300713190311404527845817167848982103688718636056998
7307230500063874091535649843873124733972731696151400317153853980741262
3856559117102665855668676818703956031062493194527159149245532930545654
4401127480129709999541931989409080416563324524757147869014726780159355
2386115501348035264934720193790268107107491703332226844753335720832431
9360923828934583680601060115061698097530783422773183292479049825247307
7637592724787465608477820373446969953364701797267771758512566055119913
1504891101451037862738167250955837389733598993664809941164205702637090
279242767544565229087538682506419718265533447265625e-324

4.940656458412465441765687928682213723650598026143247644255856825006755
0727020875186529983636163599237979656469544571773092665671035593979639
8774796010781878126300713190311404527845817167848982103688718636056998
7307230500063874091535649843873124733972731696151400317153853980741262
3856559117102665855668676818703956031162493194527159149245532930545654
4401127480129709999541931989409080416563324524757147869014726780159355
2386115501348035264934720193790268107107491703332226844753335720832431
9360923828934583680601060115061698097530783422773183292479049825247307
7637592724787465608477820373446969953364701797267771758512566055119913
1504891101451037862738167250955837389733598993664809941164205702637090
279242767544565229087538682506419718265533447265625e-324

Need more time?  Did you correctly pick the first one?

Using BigDecimal as the numerical back store removes the concern for this and many other 
numerical anomalies at the cost of the "10.0" is not always equivalent to 
"10" issue.  However, the latter problem is manageable whereas predicting string to 
double conversion is much less so.

The equals / hashCode contracts of BigDecimal and long-standing and should by 
built-upon by the JSON API.  You can define an alternative equals / hashCode 
definition (the hashcode in the pdf isn't quite right, the a numeric zero 
needs to be handled separately), but that introduces other kinds of anomalies.
----------
[1] http://crazyjavahacking.org/jsonProcessingReview.pdf



[json-processing-spec users] BigDecimal for equals()/hashCode()

Jitendra Kotamraju 01/24/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/24/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jitendra Kotamraju 01/24/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/25/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/25/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jörn Horstmann 01/24/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jitendra Kotamraju 01/24/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/25/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/28/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/28/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jitendra Kotamraju 01/29/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jonathan Fuerth 01/29/2013

[json-processing-spec users] Re: BigDecimal for equals()/hashCode()

Jitendra Kotamraju 01/29/2013
 
 
Close
loading
Please Confirm
Close