blob: b7ee6b9a87aaea97ba50181b54cb5e1b3e7911c9 [file] [log] [blame]
// 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;
}
}
}
}
}