diff options
Diffstat (limited to 'src/arrow/csharp/src/Apache.Arrow/DecimalUtility.cs')
-rw-r--r-- | src/arrow/csharp/src/Apache.Arrow/DecimalUtility.cs | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/arrow/csharp/src/Apache.Arrow/DecimalUtility.cs b/src/arrow/csharp/src/Apache.Arrow/DecimalUtility.cs new file mode 100644 index 000000000..b7ee6b9a8 --- /dev/null +++ b/src/arrow/csharp/src/Apache.Arrow/DecimalUtility.cs @@ -0,0 +1,162 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Numerics; + +namespace Apache.Arrow +{ + /// <summary> + /// This is semi-optimised best attempt at converting to / from decimal and the buffers + /// </summary> + internal static class DecimalUtility + { + private static readonly BigInteger _maxDecimal = new BigInteger(decimal.MaxValue); + private static readonly BigInteger _minDecimal = new BigInteger(decimal.MinValue); + private static readonly ulong[] s_powersOfTen = + { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, + 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000, + 1000000000000000000, 10000000000000000000 + }; + + private static int PowersOfTenLength => s_powersOfTen.Length - 1; + + internal static decimal GetDecimal(in ArrowBuffer valueBuffer, int index, int scale, int byteWidth) + { + int startIndex = index * byteWidth; + ReadOnlySpan<byte> value = valueBuffer.Span.Slice(startIndex, byteWidth); + BigInteger integerValue; + +#if NETCOREAPP + integerValue = new BigInteger(value); +#else + integerValue = new BigInteger(value.ToArray()); +#endif + + if (integerValue > _maxDecimal || integerValue < _minDecimal) + { + BigInteger scaleBy = BigInteger.Pow(10, scale); + BigInteger integerPart = BigInteger.DivRem(integerValue, scaleBy, out BigInteger fractionalPart); + if (integerPart > _maxDecimal || integerPart < _minDecimal) // decimal overflow, not much we can do here - C# needs a BigDecimal + { + throw new OverflowException($"Value: {integerPart} too big or too small to be represented as a decimal"); + } + return (decimal)integerPart + DivideByScale(fractionalPart, scale); + } + else + { + return DivideByScale(integerValue, scale); + } + } + + private static decimal DivideByScale(BigInteger integerValue, int scale) + { + decimal result = (decimal)integerValue; // this cast is safe here + int drop = scale; + while (drop > PowersOfTenLength) + { + result /= s_powersOfTen[PowersOfTenLength]; + drop -= PowersOfTenLength; + } + + result /= s_powersOfTen[drop]; + return result; + } + + internal static void GetBytes(decimal value, int precision, int scale, int byteWidth, Span<byte> bytes) + { + // create BigInteger from decimal + BigInteger bigInt; + int[] decimalBits = decimal.GetBits(value); + int decScale = (decimalBits[3] >> 16) & 0x7F; +#if NETCOREAPP + Span<byte> bigIntBytes = stackalloc byte[12]; + + for (int i = 0; i < 3; i++) + { + int bit = decimalBits[i]; + Span<byte> intBytes = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(intBytes, bit)) + throw new OverflowException($"Could not extract bytes from int {bit}"); + + for (int j = 0; j < 4; j++) + { + bigIntBytes[4 * i + j] = intBytes[j]; + } + } + bigInt = new BigInteger(bigIntBytes); +#else + byte[] bigIntBytes = new byte[12]; + for (int i = 0; i < 3; i++) + { + int bit = decimalBits[i]; + byte[] intBytes = BitConverter.GetBytes(bit); + for (int j = 0; j < intBytes.Length; j++) + { + bigIntBytes[4 * i + j] = intBytes[j]; + } + } + bigInt = new BigInteger(bigIntBytes); +#endif + + if (value < 0) + { + bigInt = -bigInt; + } + + // validate precision and scale + if (decScale > scale) + throw new OverflowException($"Decimal scale cannot be greater than that in the Arrow vector: {decScale} != {scale}"); + + if (bigInt >= BigInteger.Pow(10, precision)) + throw new OverflowException($"Decimal precision cannot be greater than that in the Arrow vector: {value} has precision > {precision}"); + + if (decScale < scale) // pad with trailing zeros + { + bigInt *= BigInteger.Pow(10, scale - decScale); + } + + // extract bytes from BigInteger + if (bytes.Length != byteWidth) + { + throw new OverflowException($"ValueBuffer size not equal to {byteWidth} byte width: {bytes.Length}"); + } + + int bytesWritten; +#if NETCOREAPP + if (!bigInt.TryWriteBytes(bytes, out bytesWritten, false, !BitConverter.IsLittleEndian)) + throw new OverflowException("Could not extract bytes from integer value " + bigInt); +#else + byte[] tempBytes = bigInt.ToByteArray(); + tempBytes.CopyTo(bytes); + bytesWritten = tempBytes.Length; +#endif + + if (bytes.Length > byteWidth) + { + throw new OverflowException($"Decimal size greater than {byteWidth} bytes: {bytes.Length}"); + } + + if (bigInt.Sign == -1) + { + for (int i = bytesWritten; i < byteWidth; i++) + { + bytes[i] = 255; + } + } + } + } +} |