|
1 | 1 | using System.Globalization; |
| 2 | +using System.Numerics; |
2 | 3 | using Google.Protobuf; |
3 | 4 | using Google.Protobuf.WellKnownTypes; |
4 | 5 |
|
@@ -74,61 +75,33 @@ internal static TypedValue Double(this double value) => |
74 | 75 |
|
75 | 76 | internal static TypedValue Decimal(this decimal value, byte precision, byte scale) |
76 | 77 | { |
77 | | - var bits0 = decimal.GetBits(value); |
78 | | - var fracDigits0 = (bits0[3] >> 16) & 0xFF; |
| 78 | + var bits = decimal.GetBits(value); |
| 79 | + var scale0 = (bits[3] >> 16) & 0xFF; |
| 80 | + var isNegative = (bits[3] & unchecked((int)0x80000000)) != 0; |
79 | 81 |
|
80 | | - var absInt0 = decimal.Truncate(Math.Abs(value)); |
81 | | - var integerDigits0 = absInt0 == 0m |
82 | | - ? 1 |
83 | | - : absInt0.ToString(CultureInfo.InvariantCulture).Length; |
84 | | - |
85 | | - if (fracDigits0 > scale) |
86 | | - throw new OverflowException( |
87 | | - $"Decimal scale overflow: fractional digits {fracDigits0} exceed allowed {scale} for DECIMAL({precision},{scale}). Value={value}"); |
88 | | - |
89 | | - if (integerDigits0 > precision - scale) |
| 82 | + if (scale0 > scale) |
90 | 83 | throw new OverflowException( |
91 | | - $"Decimal precision overflow: integer digits {integerDigits0} exceed allowed {precision - scale} for DECIMAL({precision},{scale}). Value={value}"); |
92 | | - |
93 | | - var rounded = Math.Round(value, scale, MidpointRounding.ToEven); |
| 84 | + $"Decimal scale overflow: fractional digits {scale0} exceed allowed {scale} for DECIMAL({precision},{scale}). Value={value}"); |
94 | 85 |
|
95 | | - var rb = decimal.GetBits(rounded); |
96 | | - var roundedScale = (rb[3] >> 16) & 0xFF; |
97 | | - var negative = (rb[3] & unchecked((int)0x80000000)) != 0; |
| 86 | + var lo = (uint)bits[0]; |
| 87 | + var mid = (uint)bits[1]; |
| 88 | + var hi = (uint)bits[2]; |
| 89 | + var mantissa = ((BigInteger)hi << 64) | ((BigInteger)mid << 32) | lo; |
98 | 90 |
|
99 | | - var unscaled = new decimal(rb[0], rb[1], rb[2], false, 0); |
100 | | - |
101 | | - var delta = scale - roundedScale; |
| 91 | + var delta = scale - scale0; |
102 | 92 | if (delta > 0) |
103 | | - { |
104 | | - for (var i = 0; i < delta; i++) |
105 | | - unscaled *= 10m; |
106 | | - } |
107 | | - else if (delta < 0) |
108 | | - { |
109 | | - for (var i = 0; i < -delta; i++) |
110 | | - unscaled /= 10m; |
111 | | - } |
112 | | - |
113 | | - var ub = decimal.GetBits(unscaled); |
114 | | - var low = ((ulong)ub[1] << 32) + (uint)ub[0]; |
115 | | - var high = (ulong)ub[2]; |
| 93 | + mantissa *= BigInteger.Pow(10, delta); |
116 | 94 |
|
117 | | - unchecked |
118 | | - { |
119 | | - if (negative) |
120 | | - { |
121 | | - low = ~low; |
122 | | - high = ~high; |
| 95 | + var totalDigits = mantissa.IsZero ? 1 : mantissa.ToString(CultureInfo.InvariantCulture).Length; |
| 96 | + if (totalDigits > precision) |
| 97 | + throw new OverflowException( |
| 98 | + $"Decimal precision overflow: total digits {totalDigits} exceed allowed {precision} for DECIMAL({precision},{scale}). Value={value}"); |
123 | 99 |
|
124 | | - if (low == (ulong)-1L) |
125 | | - { |
126 | | - high += 1; |
127 | | - } |
| 100 | + if (isNegative) mantissa = -mantissa; |
128 | 101 |
|
129 | | - low += 1; |
130 | | - } |
131 | | - } |
| 102 | + var mod128 = (mantissa % (BigInteger.One << 128) + (BigInteger.One << 128)) % (BigInteger.One << 128); |
| 103 | + var low = (ulong)(mod128 & ((BigInteger.One << 64) - 1)); |
| 104 | + var high = (ulong)(mod128 >> 64); |
132 | 105 |
|
133 | 106 | return new TypedValue |
134 | 107 | { |
|
0 commit comments