@@ -205,6 +205,7 @@ internal DateTime DateTime
205
205
}
206
206
}
207
207
208
+ #region Decimal
208
209
internal decimal Decimal
209
210
{
210
211
get
@@ -215,64 +216,44 @@ internal decimal Decimal
215
216
{
216
217
if ( _value . _numericInfo . _data4 != 0 || _value . _numericInfo . _scale > 28 )
217
218
{
219
+ // Only removing trailing zeros from a decimal part won't hit its value!
218
220
if ( _value . _numericInfo . _scale > 0 )
219
221
{
220
- SqlDecimal sqlValue = new ( _value . _numericInfo . _precision ,
221
- _value . _numericInfo . _scale ,
222
- _value . _numericInfo . _positive ,
223
- _value . _numericInfo . _data1 ,
224
- _value . _numericInfo . _data2 ,
225
- _value . _numericInfo . _data3 ,
226
- _value . _numericInfo . _data4 ) ;
227
-
228
- // Remove trail zeros after the point from the end to calculate the valuable floating part
229
- ReadOnlySpan < char > strValue = sqlValue . ToString ( ) . AsSpan ( ) . TrimEnd ( '0' ) ;
230
- int integral = strValue . IndexOf ( '.' ) ;
231
- int scale = strValue . Length - integral - 1 ;
232
- int precision = integral + scale + ( sqlValue . IsPositive ? 0 : - 1 ) ;
233
-
234
- // Out of range
235
- if ( ( precision > 29 || scale > 28 ) && ! ( precision == 29 && scale == 28 ) )
236
- {
237
- throw new OverflowException ( SQLResource . ConversionOverflowMessage ) ;
238
- }
239
- else
222
+ int zeroCnt = FindTrailingZerosAndPrec ( ( uint ) _value . _numericInfo . _data1 , ( uint ) _value . _numericInfo . _data2 ,
223
+ ( uint ) _value . _numericInfo . _data3 , ( uint ) _value . _numericInfo . _data4 ,
224
+ _value . _numericInfo . _scale , out int precision ) ;
225
+
226
+ int minScale = _value . _numericInfo . _scale - zeroCnt ; // minimum possible sacle after removing the trailing zeros.
227
+
228
+ if ( zeroCnt > 0 && minScale <= 28 && precision <= 29 )
240
229
{
241
- // Precision could be 28 or 29
242
- // ex: (precision == 29 && scale == 28)
243
- // valid: (+/-)7.1234567890123456789012345678
244
- // invalid: (+/-)8.1234567890123456789012345678
245
- bool tryMaxPrec = integral == 1 || ( integral == 2 && ! _value . _numericInfo . _positive ) ;
246
- int precConvert = 29 ;
247
- int scaleConvert ;
248
-
249
- if ( ! tryMaxPrec && precision != 29 )
230
+ SqlDecimal sqlValue = new ( _value . _numericInfo . _precision , _value . _numericInfo . _scale , _value . _numericInfo . _positive ,
231
+ _value . _numericInfo . _data1 , _value . _numericInfo . _data2 ,
232
+ _value . _numericInfo . _data3 , _value . _numericInfo . _data4 ) ;
233
+
234
+ int integral = precision - minScale ;
235
+ int newPrec = 29 ;
236
+
237
+ if ( integral != 1 && precision != 29 )
250
238
{
251
- precConvert = 28 ;
239
+ newPrec = 28 ;
252
240
}
253
- scaleConvert = precConvert - ( precision - scale ) ;
254
241
255
242
try
256
243
{
257
- return SqlDecimal . ConvertToPrecScale ( sqlValue , precConvert , scaleConvert ) . Value ;
244
+ // Precision could be 28 or 29
245
+ // ex: (precision == 29 && scale == 28)
246
+ // valid: (+/-)7.1234567890123456789012345678
247
+ // invalid: (+/-)8.1234567890123456789012345678
248
+ return SqlDecimal . ConvertToPrecScale ( sqlValue , newPrec , newPrec - integral ) . Value ;
258
249
}
259
250
catch ( OverflowException )
260
251
{
261
- if ( tryMaxPrec && scale < 28 )
262
- {
263
- return SqlDecimal . ConvertToPrecScale ( sqlValue , precConvert - 1 , scaleConvert ) . Value ;
264
- }
265
- else
266
- {
267
- throw ;
268
- }
252
+ throw new OverflowException ( SQLResource . ConversionOverflowMessage ) ;
269
253
}
270
254
}
271
255
}
272
- else
273
- {
274
- throw new OverflowException ( SQLResource . ConversionOverflowMessage ) ;
275
- }
256
+ throw new OverflowException ( SQLResource . ConversionOverflowMessage ) ;
276
257
}
277
258
return new decimal ( _value . _numericInfo . _data1 , _value . _numericInfo . _data2 , _value . _numericInfo . _data3 , ! _value . _numericInfo . _positive , _value . _numericInfo . _scale ) ;
278
259
}
@@ -291,6 +272,85 @@ internal decimal Decimal
291
272
}
292
273
}
293
274
275
+ /// <summary>
276
+ /// Returns number of trailing zeros using the supplied parameters.
277
+ /// </summary>
278
+ /// <param name="data1">An 32-bit unsigned integer which will be combined with data2, data3, and data4</param>
279
+ /// <param name="data2">An 32-bit unsigned integer which will be combined with data1, data3, and data4</param>
280
+ /// <param name="data3">An 32-bit unsigned integer which will be combined with data1, data2, and data4</param>
281
+ /// <param name="data4">An 32-bit unsigned integer which will be combined with data1, data2, and data3</param>
282
+ /// <param name="scale">The number of decimal places</param>
283
+ /// <param name="valuablePrecision">OUT |The number of digits without trailing zeros</param>
284
+ /// <returns>Number of trailing zeros</returns>
285
+ private static int FindTrailingZerosAndPrec ( uint data1 , uint data2 , uint data3 , uint data4 , byte scale , out int valuablePrecision )
286
+ {
287
+ // Make local copy of data to avoid modifying input.
288
+ uint [ ] rgulNumeric = new uint [ 4 ] { data1 , data2 , data3 , data4 } ;
289
+ int zeroCnt = 0 ; //Number of trailing zero digits
290
+ int precCnt = 0 ; //Valuable precision
291
+ uint uiRem = 0 ; //Remainder of a division by 10
292
+ int len = 4 ; // Max possible items
293
+
294
+ //Retrieve each digit from the lowest significant digit
295
+ while ( len > 1 || rgulNumeric [ 0 ] != 0 )
296
+ {
297
+ SqlDecimalDivBy ( rgulNumeric , ref len , 10 , out uiRem ) ;
298
+ if ( uiRem == 0 && precCnt == 0 )
299
+ {
300
+ zeroCnt ++ ;
301
+ }
302
+ else
303
+ {
304
+ precCnt ++ ;
305
+ }
306
+ }
307
+
308
+ if ( uiRem == 0 )
309
+ {
310
+ zeroCnt = scale ;
311
+ }
312
+
313
+ // if scale of the number has not been reached, pad remaining number with zeros.
314
+ if ( zeroCnt + precCnt <= scale )
315
+ {
316
+ precCnt = scale - zeroCnt + 1 ;
317
+ }
318
+ valuablePrecision = precCnt ;
319
+ return zeroCnt ;
320
+ }
321
+
322
+ /// <summary>
323
+ /// Multi-precision one super-digit divide in place.
324
+ /// U = U / D,
325
+ /// R = U % D
326
+ /// (Length of U can decrease)
327
+ /// </summary>
328
+ /// <param name="data">InOut | U</param>
329
+ /// <param name="len">InOut | Number of items with non-zero value in U between 1 to 4</param>
330
+ /// <param name="divisor">In | D</param>
331
+ /// <param name="remainder">Out | R</param>
332
+ private static void SqlDecimalDivBy ( uint [ ] data , ref int len , uint divisor , out uint remainder )
333
+ {
334
+ uint uiCarry = 0 ;
335
+ ulong ulAccum ;
336
+ ulong ulDivisor = ( ulong ) divisor ;
337
+ int iLen = len ;
338
+
339
+ while ( iLen > 0 )
340
+ {
341
+ iLen -- ;
342
+ ulAccum = ( ( ( ulong ) uiCarry ) << 32 ) + ( ulong ) ( data [ iLen ] ) ;
343
+ data [ iLen ] = ( uint ) ( ulAccum / ulDivisor ) ;
344
+ uiCarry = ( uint ) ( ulAccum - ( ulong ) data [ iLen ] * ulDivisor ) ; // (ULONG) (ulAccum % divisor)
345
+ }
346
+ remainder = uiCarry ;
347
+
348
+ // Normalize multi-precision number - remove leading zeroes
349
+ while ( len > 1 && data [ len - 1 ] == 0 )
350
+ { len -- ; }
351
+ }
352
+ #endregion
353
+
294
354
internal double Double
295
355
{
296
356
get
0 commit comments