Repository navigation
Conversation
…ding NUMERIC/DECIMAL with precision 19-38 were converted to System.Decimal while the row was decoded: - Values beyond System.Decimal (e.g. 38 digits) threw OverflowException in the middle of decoding the row. On the managed (wire) client this left the rest of the packet unread and desynchronized the connection: every later command on it failed with "ArgumentOutOfRangeException: operation=...". - Values with large scale but small magnitude overflowed too, e.g. 1.000...0 as NUMERIC(38,30). - Values with more than 28 fractional digits were rounded silently, e.g. 1E-30 as NUMERIC(38,30) was read as 0. - The native client returned BigInteger for NUMERIC(38,0)/DECIMAL(38,0) although GetFieldType() reports System.Decimal (managed returned decimal). Now the value is converted exactly: values that fit System.Decimal are returned exactly as before (smallest scale, e.g. 1234.5), on both clients. Values that do not fit are kept exactly (internal BigDecimal), so the row is always read and the connection stays usable; for those values: - GetValue/GetDecimal/GetFieldValue<decimal> throw OverflowException (only when that column is accessed), - GetString returns the exact value, - GetDouble/GetFloat return the nearest value, - GetFieldValue<BigInteger> returns integral values exactly (also for values that fit decimal; fractional values throw OverflowException). As GetFieldValue<BigInteger> and INT128 parameters share DbValue.GetInt128, an integral decimal parameter value can now be passed to an INT128 parameter (it threw InvalidCastException before).
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.
Fixes #1301.
Summary
INT128-backed
NUMERIC/DECIMALvalues are no longer converted toSystem.Decimalwhile the row is decoded. They are converted exactly instead, so a row can always be read, the connection stays usable, and nothing is rounded silently.Changes
TypeDecoder.DecodeInt128Numeric/BigDecimal.Createreturn adecimalwhen the value fits exactly, using the smallest scale. Otherwise they return an internalBigDecimal(an unscaledBigIntegerplus a scale).GdsStatement.ReadRawValue[Async]) and the native client (DbField.SetValue). The native client now also decodesNUMERIC(38, 0)/DECIMAL(38, 0)as numeric, consistent withGetFieldType()and with the managed client.decimal:GetValue,GetDecimalandGetFieldValue<decimal>throwOverflowException, for that column only.GetStringreturns the exact value, in the current culture likedecimal.GetDoubleandGetFloatreturn the nearest value.GetFieldValue<BigInteger>returns integral values exactly. This also works for values that fitdecimal; fractional values throwOverflowException.DbValue.GetDecodedValue[Async]: the native client copies decoded values into the row without the conversion thatGetValueperforms.Behavior changes (breaking)
cast('1E-30' as numeric(38, 30)): master returned0, nowGetValue/GetDecimalthrowOverflowExceptionandGetStringreturns0.000000000000000000000000000001.NUMERIC(38, 0)/DECIMAL(38, 0). Master'sGetValuereturnedBigInteger, now it returnsdecimal(asGetFieldType()always reported, and as the managed client always did). For values beyonddecimal,GetValuenow throwsOverflowException;GetFieldValue<BigInteger>still returns them exactly.decimalparameter value can now be passed to anINT128parameter, because the sameDbValue.GetInt128serves both. It threwInvalidCastExceptionbefore.All other values decode exactly as before. I compared master's decoding with this one on 2,000,000 random values (1 to 38 digits, scale 0 to 38, seeded):
decimal, identical value and scaledecimal, differentOverflowException; now exactdecimal; now exact (change 1)BigDecimalThe same comparison measured decoding time (Stopwatch, boxed like the row decoder; one machine):
NUMERIC(38,6)1234.5123asNUMERIC(38,0)Tests
BigDecimalTests(NoServer):decimal.MaxValue± 1, scale 28/29, trailing zeros,FbLongNumericsSupportTests:decimal, includingNUMERIC(38, 30)value1andNUMERIC(38, 0),GetStringis exact,GetDoubleis nearest,GetValue/GetDecimalthrow, and the connection is still usable afterwards,GetFieldValue<BigInteger>.