Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes number parsing to use BigDecimal as the backing type #453

Merged
merged 1 commit into from
Jun 1, 2020

Conversation

johnjaylward
Copy link
Contributor

@johnjaylward johnjaylward commented Dec 10, 2018

What problem does this code solve?
See issue #441 (use BigDecimal for underlying storage of decimal number types)

Risks
Medium

Changes to the API?
No, but it changes how certain API calls work for decimal/floating-point types work.

for example a JSON document list this:

{ "float":1234 }

would parse like this:

JSONObject jo = new JSONObject("{\"float\":1234}");
jo.get("float"); // used to return a Double, now returns a BigDecimal
jo.getDouble("float"); // still returns the value/object as in the old version (Double)

In general, the raw "get/opt" methods with no type information (like getDouble, getInt, optDouble, etc) are not guaranteed to be useful as the backing types were never guaranteed. However, people may be using them without the knowledge that the backing types are semi-dynamic (i.e. number types have always been backed differently if an Integer was parsed vs a float). So even a "standard" document like this:

{
"items": [
 { "floatValue": 123.4 },
 { "floatValue": 321 }
]
}

Would have a Double stored for the first floatValue and an Integer for the second floatValue before the change. After the change they would be stored as BigDecimal and Integer respectively.

Will this require a new release?
Yes.

Should the documentation be updated?
Yes. we should probably mention that the backing type has changed and how that may affect existing code.

Does it break the unit tests?
Yes, many of the tests depend on the data type stored for numbers. The tests have been updated to reflect the new backing type.

Was any code refactored in this commit?
Yes, changes were made to the number parsing methods for XML and JSONObject (they need to be kept in sync for Android support)

Review status
APPROVED

@johnjaylward johnjaylward force-pushed the UseBigDecimalDefaultParse branch from 0cf737b to 3244f30 Compare December 10, 2018 21:24
@stleary
Copy link
Owner

stleary commented Dec 11, 2018

Breaks a lot of unit tests. Aren't users likely to see the same breakages?

@johnjaylward
Copy link
Contributor Author

johnjaylward commented Dec 11, 2018

Yes, I'm not sure how impactful that would be though. The APIs that are affected are the Object get/opt and not the typed ones (i.e. getString and getDouble still work the same way). The only reason the raw ones are affected is because the type changed, not the value. A programmer calling the Object get/opt methods would hopefully not be depending on a certain type like we do in the test cases.

@stleary
Copy link
Owner

stleary commented Dec 16, 2018

Is there any way to restrict this so that just values that are added as BigDecimal get the new behavior?

@johnjaylward
Copy link
Contributor Author

johnjaylward commented Dec 16, 2018

Values added as big decimal are already handled as big decimal. This is strictly to change how the parsing Of JSON text works, not how JSONObject.{put,get*,opt*} work.

@PeerHeijnen
Copy link

The behavior for this conversion has changed from 20180813 to 20190722.

System.out.println( new JSONObject( new JSONObject().put( "test", new BigDecimal( "348.8" ) ).toString() ).getBigDecimal( "test" ) );

Output for 20180813: 384.8

Output for 20190722: 348.80000000000001136868377216160297393798828125

Instead of chaging this behavior by default, would it be possible to choose a method for parsing that is suitable to the application?

@johnjaylward
Copy link
Contributor Author

johnjaylward commented Apr 8, 2020

@PeerHeijnen, the main issue here is that you do the following steps:

  1. Create a JSONObject with a single BigDecimal field
  2. Convert the JSONObject to a JSON Text
  3. Convert the JSON Text back to a JSONObject. The parser will read the number as a double
  4. Read a double value as a BigDecimal.

The conversion from double to BigDecimal uses the exact double value which is the output you see now. If you set the BigDecimal scale to 1 or 2, you would lose the extra info you don't want from the double.

This PR is meant to fix the round tripping that causes the issue you are seeing. Currently in master the backing store is always an int, long, or double which makes round trips more difficult as the double is inaccurate as a storage means for decimal floating point.

Another option would be to add another getter with a scale parameter so you can configure the scale you wish to read right away as one call.

BigDecimal bd = JSONObject.getBigDecimal("test", 4);
System.out.println(bd); // would print 348.8000 abbreviated to 348.8

Using today's API, this would look like:

BigDecimal bd = JSONObject.getBigDecimal("test").setScale(4, RoundingMode.HALF_EVEN);
System.out.println(bd); // would print 348.8000 abbreviated to 348.8

As a side note, it's usually recommended to set a scale on all your BigDecimal uses to prevent issues like this popping up.

@PeerHeijnen
Copy link

Sure, it was not ment as criticism. I just noted the change in behavior, be it intentional or not.

The code I shared was not actual production code, just a short version of how the conversion happens as you explained.

I was just wondering whether people may experience side effect from the PR. For me, it solves my problems I have now perfectly, so thanks for the effort! :)

@johnjaylward johnjaylward force-pushed the UseBigDecimalDefaultParse branch from 429a2ef to 287e40b Compare May 22, 2020 18:38
@johnjaylward
Copy link
Contributor Author

rebased to support the new project structure

@johnjaylward johnjaylward force-pushed the UseBigDecimalDefaultParse branch from 287e40b to e46ddd0 Compare May 26, 2020 14:03
* updated tests to support BigDecimal as the backing type for numbers
* updated some test resource handling to java7 try-with-resources format
* cleaned up some other minor compiler warnings
@johnjaylward johnjaylward force-pushed the UseBigDecimalDefaultParse branch from e46ddd0 to 56d33b8 Compare May 26, 2020 14:10
@stleary
Copy link
Owner

stleary commented May 29, 2020

Starting 3 day comment window

@stleary stleary merged commit 19bb6fd into stleary:master Jun 1, 2020
@johnjaylward
Copy link
Contributor Author

@TheMatthew Do you still have the benchmark tests you were running for the other improvements we made? I was wondering if you could spin them up for this change... I should have probably thought of doing that in the last 1.5 years this has been open, but it just occurred to me now

@johnjaylward johnjaylward deleted the UseBigDecimalDefaultParse branch June 1, 2020 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants