1757 - Decimal precision support - #2377
Open
ValeryVerkhoturov wants to merge 1 commit into
Open
Conversation
Author
|
Also you may check my tests of writing and reading Excel file with decimal lib dependencies https://github.com/ValeryVerkhoturov/excelize-decimal-test |
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.
PR Details
Description
SetCellValuenow recognizes arbitrary precision decimal types and stores themas numbers rather than falling through to
fmt.Sprintand becoming a string.Detection is structural, so no dependency is added. Both of the widely used
decimal packages expose the same two methods on a value receiver, which is
enough to match on:
github.com/shopspring/decimalhasFloat64() (f float64, exact bool)andgithub.com/govalues/decimalhasFloat64() (f float64, ok bool), so bothsatisfy it as-is, as do pointers to them.
The cell is written from
String()rather than fromFloat64(), so the digitsnever pass through a
float64and nothing is rounded on the way out:That is the same shape excelize already emits for a
float64: notattribute, which is the implicit number type.
Covered paths:
SetCellValue, and thereforeSetSheetRow/SetSheetColStreamWriter.SetRow, viasetCellValFuncFallbacks are conservative, so nothing that works today changes:
still renders through the existing
fmt.Sprintpath.String()does not yield somethingisNumericaccepts, the value fallsthrough to the current string path. This matters because
*math/big.Ratalsosatisfies the interface, but its
String()returns"3/2", so it stays astring exactly as it does now.
One behavior change worth calling out explicitly: a type that implements that
method set and was previously stored as a string will now be stored as a
number. That is the point of the change, but it is a visible difference for
anyone who was relying on the old stringly behavior.
Related Issue
#1757
Motivation and Context
Issue #1757 asks for storing decimal precision values natively, for financial
data where a
float64is not an acceptable carrier.Today the only two options both lose something. Passing
decimal.InexactFloat64()produces a number cell but silently rounds once anamount needs more than about 15 significant digits. Passing the decimal object
itself hits the
defaultbranch ofSetCellValue, which stringifies it, so thecell becomes text: Excel will not sum it,
SUMskips it, and the column is nota number at all.
This change gives the third option, a number cell holding the exact digits, with
no dependency on either decimal package.
On precision, and where it does and does not survive
This is worth being precise about, because the two audiences differ.
In the Excel GUI, precision beyond ~15 significant digits is still lost.
Excel stores numbers as IEEE-754 doubles and displays at most 15 significant
digits under the General format, so
1234567890.12345678shows as1234567890.12346, and a workbook opened and re-saved in Excel comes backrounded. No library-side change can avoid that; it is a property of the file
format's consumer.
Read back programmatically, nothing is lost. The exact digits are what land
in the XML, so a reader that asks for the raw value gets the original decimal
back:
GetRowstakes the same option. Note the qualifier: withoutRawCellValuetheGeneral number format is applied on read and the value is rounded to what Excel
would display, so the raw option is required on the read side of a round trip.
That second case is the one that matters most in practice, and it is the
motivating workload here: pipelines that write a workbook, put it in S3, read it
back elsewhere, and load it into a database column typed
DECIMAL. Nothing inthat path opens Excel, so the bytes are never re-rounded, and with this change
the amount that reaches the database is the amount that was written. Before it,
the same pipeline had to choose between a lossy
float64and a text column itthen had to reparse.
How Has This Been Tested
Environment: go1.25.11, darwin/arm64.
TestSetCellValueDecimaladded incell_test.go, covering the value form,the pointer form, a nil pointer, a
*big.Ratnon-regression to confirm anon-numeric
String()still stores as a string, theStreamWriterpath, andthe inline-string overwrite fix.
go test .passes.gofmtandgo vetclean.Verified against the real packages, not only against a mock implementing the
interface. In a separate module depending on
shopspring/decimal v1.4.0andgovalues/decimal v0.1.36with areplaceonto this branch, both produce<c r="A1"><v>...</v></c>with notattribute, identical to thefloat64path, and round-trip tests write a workbook, save it, reopen it with
OpenFileand read it back:
19.991234567890.123456781234567890.12345679007199254740993.019007199254740994The same module pins the read-side behavior described above: raw reads return
the exact digits after a full save and reopen, including through the stream
writer, while a formatted read returns the rounded General-format value.
Types of changes
Checklist