Keep a float32 a float - #18
Merged
Merged
Conversation
The parser read float32 and float64 alike into a double and never reported which, so untyped binding turned a Float into a Double and its text into that of the widened double (0.10000000149011612 for 0.1f). A float32 is now reported as NumberType.FLOAT and NumberTypeFP.FLOAT32, as Jackson's CBOR parser does, and its text is the float's own; a float64 reports DOUBLE64. Both stay in one double field, which holds every float exactly.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Add focused float32 getDecimalValue() regression coverage.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Preserves MessagePack float32 semantics during Jackson parsing while retaining float64 behavior.
Changes:
- Adds width-aware floating-point types and values.
- Preserves float32 text, decimal conversion, and map keys.
- Updates tests and design documentation.
| File | Summary |
|---|---|
src/test/java/org/komamitsu/jackson/dataformat/msgpack/MessagePackParserTest.java |
Adds and updates floating-point behavior tests. |
src/main/java/org/komamitsu/jackson/dataformat/msgpack/MessagePackParser.java |
Implements float32-aware parsing and accessors. |
docs/DESIGN.md |
Documents floating-point width preservation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+651
to
+656
| case FLOAT: | ||
| case DOUBLE: | ||
| if (!Double.isFinite(doubleValue)) { | ||
| return _reportError("Cannot convert non-finite double (" + doubleValue + ") to BigDecimal"); | ||
| } | ||
| return BigDecimal.valueOf(doubleValue); | ||
| return new BigDecimal(floatingText()); |
Owner
Author
There was a problem hiding this comment.
Added in 0fe509a: aFloat32IsReportedAsAFloat now asserts getDecimalValue() is new BigDecimal("0.1") for the float32 0.1f (and for the float64 0.1). With the old widened conversion (BigDecimal.valueOf(doubleValue)) put back temporarily, it fails with 0.10000000149011612.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

A behaviour change: a float32 on the wire now reads back as a float.
Why
The parser read float32 and float64 alike into a
doubleand never told Jackson which it was (nogetNumberTypeFP(), andgetNumberType()alwaysDOUBLE). So untyped binding (Object,Map<String, Object>,JsonNode) turned aFloatinto aDouble, and its text became that of the widened double:Jackson's CBOR parser reports float32 as
FLOAT32(CBORParser.getNumberTypeFP()), and as documented, MessagePack libraries in languages with a 32-bit float type (Go, Rust, C#) keep it as one. msgpack-java core does not (unpackValue()widens every float to a double), but compatibility with that stack is not a goal here.The change
NumberType.FLOATandNumberTypeFP.FLOAT32; a float64 asDOUBLEandDOUBLE64.getNumberValue()returns aFloatfor a float32.getDecimalValue()follow the float's own shortest text (0.1), not the widened double's.doublefield, which holds every float exactly, so the numeric conversions are shared.DESIGN.md describes it in the parser section.
Tests
Four new tests in
MessagePackParserTest, each failing onmain: the accessors for a float32 and a float64, untyped reads (Float,Double,Float.NaN), aFloatround trip throughMap<String, Object>, and a float32 key's name. Two existing tests pinned the old behaviour (a float32 read untyped as aDouble) and now expect aFloat../gradlew clean build, 227 tests.Performance
MsgpackReadBenchmark, 5 forks x 10 iterations x 2 s, both orders:The MessagePack difference is within error both times. The JSON control, whose code is identical, moved about 2% towards whichever jar ran first, so the MessagePack/JSON ratio flipped with the order. No measurable effect. The fixture has no floats, so this checks that the extra case in every accessor switch does not slow the rest of the parser.