This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 2628d495ca GH-38483: [C#] Add support for more decimal conversions
(#38508)
2628d495ca is described below
commit 2628d495ca99a892dca50019f4e72f087dc5aac7
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Mon Oct 30 07:00:52 2023 -0700
GH-38483: [C#] Add support for more decimal conversions (#38508)
### What changes are included in this PR?
Adds support for decimal128 <-> string and decimal256 <-> string.
Adds support for decimal256 <-> SqlDecimal.
### Are these changes tested?
Yes
### Are there any user-facing changes?
APIs have been added to support these conversions.
* Closes: #38483
Authored-by: Curt Hagenlocher <[email protected]>
Signed-off-by: Curt Hagenlocher <[email protected]>
---
csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs | 40 ++++
csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs | 98 ++++++++-
csharp/src/Apache.Arrow/DecimalUtility.cs | 229 +++++++++++++++++++++
.../Apache.Arrow.Tests/Decimal128ArrayTests.cs | 89 ++++++++
.../Apache.Arrow.Tests/Decimal256ArrayTests.cs | 223 +++++++++++++++++++-
.../test/Apache.Arrow.Tests/DecimalUtilityTests.cs | 85 ++++++++
6 files changed, 761 insertions(+), 3 deletions(-)
diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs
b/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs
index 7b147f5124..01724e2acd 100644
--- a/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs
@@ -64,6 +64,37 @@ namespace Apache.Arrow
return Instance;
}
+ public Builder Append(string value)
+ {
+ if (value == null)
+ {
+ AppendNull();
+ }
+ else
+ {
+ Span<byte> bytes = stackalloc byte[DataType.ByteWidth];
+ DecimalUtility.GetBytes(value, DataType.Precision,
DataType.Scale, ByteWidth, bytes);
+ Append(bytes);
+ }
+
+ return Instance;
+ }
+
+ public Builder AppendRange(IEnumerable<string> values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ foreach (string s in values)
+ {
+ Append(s);
+ }
+
+ return Instance;
+ }
+
#if !NETSTANDARD1_3
public Builder Append(SqlDecimal value)
{
@@ -120,6 +151,15 @@ namespace Apache.Arrow
return DecimalUtility.GetDecimal(ValueBuffer, index, Scale,
ByteWidth);
}
+ public string GetString(int index)
+ {
+ if (IsNull(index))
+ {
+ return null;
+ }
+ return DecimalUtility.GetString(ValueBuffer, index, Precision,
Scale, ByteWidth);
+ }
+
#if !NETSTANDARD1_3
public SqlDecimal? GetSqlDecimal(int index)
{
diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs
b/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs
index fb4cd6be39..f314c2d6eb 100644
--- a/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs
@@ -15,8 +15,10 @@
using System;
using System.Collections.Generic;
+#if !NETSTANDARD1_3
+using System.Data.SqlTypes;
+#endif
using System.Diagnostics;
-using System.Numerics;
using Apache.Arrow.Arrays;
using Apache.Arrow.Types;
@@ -61,6 +63,68 @@ namespace Apache.Arrow
return Instance;
}
+ public Builder Append(string value)
+ {
+ if (value == null)
+ {
+ AppendNull();
+ }
+ else
+ {
+ Span<byte> bytes = stackalloc byte[DataType.ByteWidth];
+ DecimalUtility.GetBytes(value, DataType.Precision,
DataType.Scale, ByteWidth, bytes);
+ Append(bytes);
+ }
+
+ return Instance;
+ }
+
+ public Builder AppendRange(IEnumerable<string> values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ foreach (string s in values)
+ {
+ Append(s);
+ }
+
+ return Instance;
+ }
+
+#if !NETSTANDARD1_3
+ public Builder Append(SqlDecimal value)
+ {
+ Span<byte> bytes = stackalloc byte[DataType.ByteWidth];
+ DecimalUtility.GetBytes(value, DataType.Precision,
DataType.Scale, bytes);
+ if (!value.IsPositive)
+ {
+ var span = bytes.CastTo<long>();
+ span[2] = -1;
+ span[3] = -1;
+ }
+
+ return Append(bytes);
+ }
+
+ public Builder AppendRange(IEnumerable<SqlDecimal> values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ foreach (SqlDecimal d in values)
+ {
+ Append(d);
+ }
+
+ return Instance;
+ }
+#endif
+
public Builder Set(int index, decimal value)
{
Span<byte> bytes = stackalloc byte[DataType.ByteWidth];
@@ -92,5 +156,37 @@ namespace Apache.Arrow
return DecimalUtility.GetDecimal(ValueBuffer, index, Scale,
ByteWidth);
}
+
+ public string GetString(int index)
+ {
+ if (IsNull(index))
+ {
+ return null;
+ }
+ return DecimalUtility.GetString(ValueBuffer, index, Precision,
Scale, ByteWidth);
+ }
+
+#if !NETSTANDARD1_3
+ public bool TryGetSqlDecimal(int index, out SqlDecimal? value)
+ {
+ if (IsNull(index))
+ {
+ value = null;
+ return true;
+ }
+
+ const int longWidth = 4;
+ var span = ValueBuffer.Span.CastTo<long>().Slice(index *
longWidth);
+ if ((span[2] == 0 && span[3] == 0) ||
+ (span[2] == -1 && span[3] == -1))
+ {
+ value = DecimalUtility.GetSqlDecimal128(ValueBuffer, 2 *
index, Precision, Scale);
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+#endif
}
}
diff --git a/csharp/src/Apache.Arrow/DecimalUtility.cs
b/csharp/src/Apache.Arrow/DecimalUtility.cs
index 35e56ff65e..bb3f0834fc 100644
--- a/csharp/src/Apache.Arrow/DecimalUtility.cs
+++ b/csharp/src/Apache.Arrow/DecimalUtility.cs
@@ -76,6 +76,113 @@ namespace Apache.Arrow
}
}
+#if NETCOREAPP
+ internal unsafe static string GetString(in ArrowBuffer valueBuffer,
int index, int precision, int scale, int byteWidth)
+ {
+ int startIndex = index * byteWidth;
+ ReadOnlySpan<byte> value = valueBuffer.Span.Slice(startIndex,
byteWidth);
+ BigInteger integerValue = new BigInteger(value);
+ if (scale == 0)
+ {
+ return integerValue.ToString();
+ }
+
+ bool negative = integerValue.Sign < 0;
+ if (negative)
+ {
+ integerValue = -integerValue;
+ }
+
+ int start = scale + 3;
+ Span<char> result = stackalloc char[start + precision];
+ if (!integerValue.TryFormat(result.Slice(start), out int
charsWritten) || charsWritten > precision)
+ {
+ throw new OverflowException($"Value: {integerValue} cannot be
formatted");
+ }
+
+ if (scale >= charsWritten)
+ {
+ int length = charsWritten;
+ result[++length] = '0';
+ result[++length] = '.';
+ while (scale > length - 2)
+ {
+ result[++length] = '0';
+ }
+ start = charsWritten + 1;
+ charsWritten = length;
+ }
+ else
+ {
+ result.Slice(start, charsWritten -
scale).CopyTo(result.Slice(--start));
+ charsWritten++;
+ result[charsWritten + 1] = '.';
+ }
+
+ if (negative)
+ {
+ result[--start] = '-';
+ charsWritten++;
+ }
+
+ return new string(result.Slice(start, charsWritten));
+ }
+#else
+ internal unsafe static string GetString(in ArrowBuffer valueBuffer,
int index, int precision, int scale, int byteWidth)
+ {
+ int startIndex = index * byteWidth;
+ ReadOnlySpan<byte> value = valueBuffer.Span.Slice(startIndex,
byteWidth);
+ BigInteger integerValue = new BigInteger(value.ToArray());
+ if (scale == 0)
+ {
+ return integerValue.ToString();
+ }
+
+ bool negative = integerValue.Sign < 0;
+ if (negative)
+ {
+ integerValue = -integerValue;
+ }
+
+ string toString = integerValue.ToString();
+ int charsWritten = toString.Length;
+ if (charsWritten > precision)
+ {
+ throw new OverflowException($"Value: {integerValue} cannot be
formatted");
+ }
+
+ char[] result = new char[precision + 2];
+ int pos = 0;
+ if (negative)
+ {
+ result[pos++] = '-';
+ }
+ if (scale >= charsWritten)
+ {
+ result[pos++] = '0';
+ result[pos++] = '.';
+ int length = 0;
+ while (scale > charsWritten + length)
+ {
+ result[pos++] = '0';
+ length++;
+ }
+ toString.CopyTo(0, result, pos, charsWritten);
+ pos += charsWritten;
+ }
+ else
+ {
+ int wholePartLength = charsWritten - scale;
+ toString.CopyTo(0, result, pos, wholePartLength);
+ pos += wholePartLength;
+ result[pos++] = '.';
+ toString.CopyTo(wholePartLength, result, pos, scale);
+ pos += scale;
+ }
+ return new string(result, 0, pos);
+ }
+#endif
+
#if !NETSTANDARD1_3
internal static SqlDecimal GetSqlDecimal128(in ArrowBuffer
valueBuffer, int index, int precision, int scale)
{
@@ -199,6 +306,128 @@ namespace Apache.Arrow
}
}
+ internal static void GetBytes(string value, int precision, int scale,
int byteWidth, Span<byte> bytes)
+ {
+ if (value == null || value.Length == 0)
+ {
+ throw new ArgumentException("numeric value may not be null or
blank", nameof(value));
+ }
+
+ int start = 0;
+ if (value[0] == '-' || value[0] == '+')
+ {
+ start++;
+ }
+ while (value[start] == '0' && start < value.Length - 1)
+ {
+ start++;
+ }
+
+ int pos = value.IndexOf('.');
+ int neededPrecision = value.Length - start;
+ int neededScale;
+ if (pos == -1)
+ {
+ neededScale = 0;
+ }
+ else
+ {
+ neededPrecision--;
+ neededScale = value.Length - pos - 1;
+ }
+
+ if (neededScale > scale)
+ {
+ throw new OverflowException($"Decimal scale cannot be greater
than that in the Arrow vector: {value} has scale > {scale}");
+ }
+ if (neededPrecision > precision)
+ {
+ throw new OverflowException($"Decimal precision cannot be
greater than that in the Arrow vector: {value} has precision > {precision}");
+ }
+
+#if NETCOREAPP
+ ReadOnlySpan<char> src = value.AsSpan();
+ Span<char> buffer = stackalloc char[precision + start + 1];
+
+ int end;
+ if (pos == -1)
+ {
+ src.CopyTo(buffer);
+ end = src.Length;
+ }
+ else
+ {
+ src.Slice(0, pos).CopyTo(buffer);
+ src.Slice(pos + 1).CopyTo(buffer.Slice(pos));
+ end = src.Length - 1;
+ }
+
+ while (neededScale < scale)
+ {
+ buffer[end++] = '0';
+ neededScale++;
+ }
+
+ if (!BigInteger.TryParse(buffer.Slice(0, end), out BigInteger
bigInt))
+ {
+ throw new ArgumentException($"Unable to parse {value} as
decimal");
+ }
+
+ if (!bigInt.TryWriteBytes(bytes, out int bytesWritten, false,
!BitConverter.IsLittleEndian))
+ {
+ throw new OverflowException("Could not extract bytes from
integer value " + bigInt);
+ }
+#else
+ char[] buffer = new char[precision + start + 1];
+
+ int end;
+ if (pos == -1)
+ {
+ value.CopyTo(0, buffer, 0, value.Length);
+ end = value.Length;
+ }
+ else
+ {
+ value.CopyTo(0, buffer, 0, pos);
+ value.CopyTo(pos + 1, buffer, pos, neededScale);
+ end = value.Length - 1;
+ }
+
+ while (neededScale < scale)
+ {
+ buffer[end++] = '0';
+ neededScale++;
+ }
+
+ if (!BigInteger.TryParse(new string(buffer, 0, end), out
BigInteger bigInt))
+ {
+ throw new ArgumentException($"Unable to parse {value} as
decimal");
+ }
+
+ byte[] tempBytes = bigInt.ToByteArray();
+ try
+ {
+ tempBytes.CopyTo(bytes);
+ }
+ catch (ArgumentException)
+ {
+ throw new OverflowException("Could not extract bytes from
integer value " + bigInt);
+ }
+ int bytesWritten = tempBytes.Length;
+#endif
+
+ if (bytes.Length > byteWidth)
+ {
+ throw new OverflowException($"Decimal size greater than
{byteWidth} bytes: {bytes.Length}");
+ }
+
+ byte fill = bigInt.Sign == -1 ? (byte)255 : (byte)0;
+ for (int i = bytesWritten; i < byteWidth; i++)
+ {
+ bytes[i] = fill;
+ }
+ }
+
#if !NETSTANDARD1_3
internal static void GetBytes(SqlDecimal value, int precision, int
scale, Span<byte> bytes)
{
diff --git a/csharp/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
b/csharp/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
index 8d7adfef42..497c9d2f6c 100644
--- a/csharp/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
@@ -16,6 +16,7 @@
using System;
#if !NETSTANDARD1_3
using System.Data.SqlTypes;
+using System.Linq;
#endif
using Apache.Arrow.Types;
using Xunit;
@@ -378,6 +379,94 @@ namespace Apache.Arrow.Tests
Assert.Null(array.GetValue(range.Length));
}
}
+
+ public class Strings
+ {
+ [Theory]
+ [InlineData(200)]
+ public void AppendString(int count)
+ {
+ // Arrange
+ const int precision = 10;
+ var builder = new Decimal128Array.Builder(new
Decimal128Type(14, precision));
+
+ // Act
+ string[] testData = new string[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (i == count - 2)
+ {
+ builder.AppendNull();
+ testData[i] = null;
+ continue;
+ }
+ SqlDecimal rnd = i * (SqlDecimal)Math.Round(new
Random().NextDouble(), 10);
+ builder.Append(rnd);
+ testData[i] = SqlDecimal.Round(rnd,
precision).ToString();
+ }
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(count, array.Length);
+ for (int i = 0; i < count; i++)
+ {
+ if (testData[i] == null)
+ {
+ Assert.Null(array.GetString(i));
+ Assert.Null(array.GetSqlDecimal(i));
+ }
+ else
+ {
+ Assert.Equal(testData[i].TrimEnd('0'),
array.GetString(i).TrimEnd('0'));
+ Assert.Equal(SqlDecimal.Parse(testData[i]),
array.GetSqlDecimal(i));
+ }
+ }
+ }
+
+ [Fact]
+ public void AppendMaxAndMinSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal128Array.Builder(new
Decimal128Type(38, 0));
+
+ // Act
+ builder.Append(SqlDecimal.MaxValue.ToString());
+ builder.Append(SqlDecimal.MinValue.ToString());
+ string maxMinusTen = (SqlDecimal.MaxValue - 10).ToString();
+ string minPlusTen = (SqlDecimal.MinValue + 10).ToString();
+ builder.Append(maxMinusTen);
+ builder.Append(minPlusTen);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(SqlDecimal.MaxValue.ToString(),
array.GetString(0));
+ Assert.Equal(SqlDecimal.MinValue.ToString(),
array.GetString(1));
+ Assert.Equal(maxMinusTen, array.GetString(2));
+ Assert.Equal(minPlusTen, array.GetString(3));
+ }
+
+ [Fact]
+ public void AppendRangeSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal128Array.Builder(new
Decimal128Type(24, 8));
+ var range = new SqlDecimal[] { 2.123M, 1.5984M,
-0.0000001M, 9878987987987987.1235407M };
+
+ // Act
+ builder.AppendRange(range.Select(d => d.ToString()));
+ builder.AppendNull();
+
+ // Assert
+ var array = builder.Build();
+ for (int i = 0; i < range.Length; i++)
+ {
+ Assert.Equal(range[i], array.GetSqlDecimal(i));
+ Assert.Equal(range[i].ToString(),
array.GetString(i).TrimEnd('0'));
+ }
+
+ Assert.Null(array.GetValue(range.Length));
+ }
+ }
#endif
}
}
diff --git a/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
b/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
index e63c39d24e..3924c73a4e 100644
--- a/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
@@ -14,7 +14,10 @@
// limitations under the License.
using System;
-using System.Collections.Generic;
+#if !NETSTANDARD1_3
+using System.Data.SqlTypes;
+using System.Linq;
+#endif
using Apache.Arrow.Types;
using Xunit;
@@ -22,6 +25,25 @@ namespace Apache.Arrow.Tests
{
public class Decimal256ArrayTests
{
+#if !NETSTANDARD1_3
+ static SqlDecimal? GetSqlDecimal(Decimal256Array array, int index)
+ {
+ SqlDecimal? result;
+ Assert.True(array.TryGetSqlDecimal(index, out result));
+ return result;
+ }
+
+ static SqlDecimal? Convert(decimal? value)
+ {
+ return value == null ? null : new SqlDecimal(value.Value);
+ }
+
+ static decimal? Convert(SqlDecimal? value)
+ {
+ return value == null ? null : value.Value.Value;
+ }
+#endif
+
public class Builder
{
public class AppendNull
@@ -45,6 +67,12 @@ namespace Apache.Arrow.Tests
Assert.Null(array.GetValue(0));
Assert.Null(array.GetValue(1));
Assert.Null(array.GetValue(2));
+
+#if !NETSTANDARD1_3
+ Assert.Null(GetSqlDecimal(array, 0));
+ Assert.Null(GetSqlDecimal(array, 1));
+ Assert.Null(GetSqlDecimal(array, 2));
+#endif
}
}
@@ -78,6 +106,9 @@ namespace Apache.Arrow.Tests
for (int i = 0; i < count; i++)
{
Assert.Equal(testData[i], array.GetValue(i));
+#if !NETSTANDARD1_3
+ Assert.Equal(Convert(testData[i]),
GetSqlDecimal(array, i));
+#endif
}
}
@@ -95,6 +126,11 @@ namespace Apache.Arrow.Tests
var array = builder.Build();
Assert.Equal(large, array.GetValue(0));
Assert.Equal(-large, array.GetValue(1));
+
+#if !NETSTANDARD1_3
+ Assert.Equal(Convert(large), GetSqlDecimal(array, 0));
+ Assert.Equal(Convert(-large), GetSqlDecimal(array, 1));
+#endif
}
[Fact]
@@ -115,6 +151,13 @@ namespace Apache.Arrow.Tests
Assert.Equal(Decimal.MinValue, array.GetValue(1));
Assert.Equal(Decimal.MaxValue - 10, array.GetValue(2));
Assert.Equal(Decimal.MinValue + 10, array.GetValue(3));
+
+#if !NETSTANDARD1_3
+ Assert.Equal(Convert(Decimal.MaxValue),
GetSqlDecimal(array, 0));
+ Assert.Equal(Convert(Decimal.MinValue),
GetSqlDecimal(array, 1));
+ Assert.Equal(Convert(Decimal.MaxValue) - 10,
GetSqlDecimal(array, 2));
+ Assert.Equal(Convert(Decimal.MinValue) + 10,
GetSqlDecimal(array, 3));
+#endif
}
[Fact]
@@ -131,6 +174,11 @@ namespace Apache.Arrow.Tests
var array = builder.Build();
Assert.Equal(fraction, array.GetValue(0));
Assert.Equal(-fraction, array.GetValue(1));
+
+#if !NETSTANDARD1_3
+ Assert.Equal(Convert(fraction), GetSqlDecimal(array, 0));
+ Assert.Equal(Convert(-fraction), GetSqlDecimal(array, 1));
+#endif
}
[Fact]
@@ -149,8 +197,11 @@ namespace Apache.Arrow.Tests
for(int i = 0; i < range.Length; i ++)
{
Assert.Equal(range[i], array.GetValue(i));
+#if !NETSTANDARD1_3
+ Assert.Equal(Convert(range[i]), GetSqlDecimal(array,
i));
+#endif
}
-
+
Assert.Null( array.GetValue(range.Length));
}
@@ -256,6 +307,174 @@ namespace Apache.Arrow.Tests
Assert.Equal(123.456M, array.GetValue(1));
}
}
+
+#if !NETSTANDARD1_3
+ public class SqlDecimals
+ {
+ [Theory]
+ [InlineData(200)]
+ public void AppendSqlDecimal(int count)
+ {
+ // Arrange
+ const int precision = 10;
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(14, precision));
+
+ // Act
+ SqlDecimal?[] testData = new SqlDecimal?[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (i == count - 2)
+ {
+ builder.AppendNull();
+ testData[i] = null;
+ continue;
+ }
+ SqlDecimal rnd = i * (SqlDecimal)Math.Round(new
Random().NextDouble(), 10);
+ builder.Append(rnd);
+ testData[i] = SqlDecimal.Round(rnd, precision);
+ }
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(count, array.Length);
+ for (int i = 0; i < count; i++)
+ {
+ Assert.Equal(testData[i], GetSqlDecimal(array, i));
+ Assert.Equal(Convert(testData[i]), array.GetValue(i));
+ }
+ }
+
+ [Fact]
+ public void AppendMaxAndMinSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(38, 0));
+
+ // Act
+ builder.Append(SqlDecimal.MaxValue);
+ builder.Append(SqlDecimal.MinValue);
+ builder.Append(SqlDecimal.MaxValue - 10);
+ builder.Append(SqlDecimal.MinValue + 10);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(SqlDecimal.MaxValue, GetSqlDecimal(array, 0));
+ Assert.Equal(SqlDecimal.MinValue, GetSqlDecimal(array, 1));
+ Assert.Equal(SqlDecimal.MaxValue - 10,
GetSqlDecimal(array, 2));
+ Assert.Equal(SqlDecimal.MinValue + 10,
GetSqlDecimal(array, 3));
+ }
+
+ [Fact]
+ public void AppendRangeSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(24, 8));
+ var range = new SqlDecimal[] { 2.123M, 1.5984M,
-0.0000001M, 9878987987987987.1235407M };
+
+ // Act
+ builder.AppendRange(range);
+ builder.AppendNull();
+
+ // Assert
+ var array = builder.Build();
+ for (int i = 0; i < range.Length; i++)
+ {
+ Assert.Equal(range[i], GetSqlDecimal(array, i));
+ Assert.Equal(Convert(range[i]), array.GetValue(i));
+ }
+
+ Assert.Null(array.GetValue(range.Length));
+ }
+ }
+
+ public class Strings
+ {
+ [Theory]
+ [InlineData(200)]
+ public void AppendString(int count)
+ {
+ // Arrange
+ const int precision = 10;
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(14, precision));
+
+ // Act
+ string[] testData = new string[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (i == count - 2)
+ {
+ builder.AppendNull();
+ testData[i] = null;
+ continue;
+ }
+ SqlDecimal rnd = i * (SqlDecimal)Math.Round(new
Random().NextDouble(), 10);
+ builder.Append(rnd);
+ testData[i] = SqlDecimal.Round(rnd,
precision).ToString();
+ }
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(count, array.Length);
+ for (int i = 0; i < count; i++)
+ {
+ if (testData[i] == null)
+ {
+ Assert.Null(array.GetString(i));
+ Assert.Null(GetSqlDecimal(array, i));
+ }
+ else
+ {
+ Assert.Equal(testData[i].TrimEnd('0'),
array.GetString(i).TrimEnd('0'));
+ Assert.Equal(SqlDecimal.Parse(testData[i]),
GetSqlDecimal(array, i));
+ }
+ }
+ }
+
+ [Fact]
+ public void AppendMaxAndMinSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(38, 0));
+
+ // Act
+ builder.Append(SqlDecimal.MaxValue.ToString());
+ builder.Append(SqlDecimal.MinValue.ToString());
+ string maxMinusTen = (SqlDecimal.MaxValue - 10).ToString();
+ string minPlusTen = (SqlDecimal.MinValue + 10).ToString();
+ builder.Append(maxMinusTen);
+ builder.Append(minPlusTen);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(SqlDecimal.MaxValue.ToString(),
array.GetString(0));
+ Assert.Equal(SqlDecimal.MinValue.ToString(),
array.GetString(1));
+ Assert.Equal(maxMinusTen, array.GetString(2));
+ Assert.Equal(minPlusTen, array.GetString(3));
+ }
+
+ [Fact]
+ public void AppendRangeSqlDecimal()
+ {
+ // Arrange
+ var builder = new Decimal256Array.Builder(new
Decimal256Type(24, 8));
+ var range = new SqlDecimal[] { 2.123M, 1.5984M,
-0.0000001M, 9878987987987987.1235407M };
+
+ // Act
+ builder.AppendRange(range.Select(d => d.ToString()));
+ builder.AppendNull();
+
+ // Assert
+ var array = builder.Build();
+ for (int i = 0; i < range.Length; i++)
+ {
+ Assert.Equal(range[i], GetSqlDecimal(array, i));
+ Assert.Equal(range[i].ToString().TrimEnd('0'),
array.GetString(i).TrimEnd('0'));
+ }
+
+ Assert.Null(array.GetValue(range.Length));
+ }
+ }
+#endif
}
}
}
diff --git a/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
b/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
index dd5f7b9d3f..677e9b6cad 100644
--- a/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
@@ -121,5 +121,90 @@ namespace Apache.Arrow.Tests
}
#endif
}
+
+ public class Strings
+ {
+ [Theory]
+ [InlineData(100.12, 10, 2, "100.12")]
+ [InlineData(100.12, 8, 3, "100.120")]
+ [InlineData(100.12, 7, 4, "100.1200")]
+ [InlineData(.12, 6, 3, "0.120")]
+ [InlineData(.0012, 5, 4, "0.0012")]
+ [InlineData(-100.12, 10, 2, "-100.12")]
+ [InlineData(-100.12, 8, 3, "-100.120")]
+ [InlineData(-100.12, 7, 4, "-100.1200")]
+ [InlineData(-.12, 6, 3, "-0.120")]
+ [InlineData(-.0012, 5, 4, "-0.0012")]
+ [InlineData(7.89, 76, 38,
"7.89000000000000000000000000000000000000")]
+ public void FromDecimal(decimal d, int precision, int scale,
string result)
+ {
+ if (precision <= 38)
+ {
+ TestFromDecimal(d, precision, scale, 16, result);
+ }
+ TestFromDecimal(d, precision, scale, 32, result);
+ }
+
+ private void TestFromDecimal(decimal d, int precision, int scale,
int byteWidth, string result)
+ {
+ var bytes = new byte[byteWidth];
+ DecimalUtility.GetBytes(d, precision, scale, byteWidth, bytes);
+ Assert.Equal(result, DecimalUtility.GetString(new
ArrowBuffer(bytes), 0, precision, scale, byteWidth));
+ }
+
+ [Theory]
+ [InlineData("100.12", 10, 2, "100.12")]
+ [InlineData("100.12", 8, 3, "100.120")]
+ [InlineData("100.12", 7, 4, "100.1200")]
+ [InlineData(".12", 6, 3, "0.120")]
+ [InlineData(".0012", 5, 4, "0.0012")]
+ [InlineData("-100.12", 10, 2, "-100.12")]
+ [InlineData("-100.12", 8, 3, "-100.120")]
+ [InlineData("-100.12", 7, 4, "-100.1200")]
+ [InlineData("-.12", 6, 3, "-0.120")]
+ [InlineData("-.0012", 5, 4, "-0.0012")]
+ [InlineData("+.0012", 5, 4, "0.0012")]
+ [InlineData("99999999999999999999999999999999999999", 38, 0,
"99999999999999999999999999999999999999")]
+ [InlineData("-99999999999999999999999999999999999999", 38, 0,
"-99999999999999999999999999999999999999")]
+ public void FromString(string s, int precision, int scale, string
result)
+ {
+ TestFromString(s, precision, scale, 16, result);
+ TestFromString(s, precision, scale, 32, result);
+ }
+
+ [Fact]
+ public void ThroughDecimal256()
+ {
+ var seventysix = new string('9', 76);
+ TestFromString(seventysix, 76, 0, 32, seventysix);
+ TestFromString("0000" + seventysix, 76, 0, 32, seventysix);
+
+ seventysix = "-" + seventysix;
+ TestFromString(seventysix, 76, 0, 32, seventysix);
+
+ var seventyseven = new string('9', 77);
+ Assert.Throws<OverflowException>(() =>
TestFromString(seventyseven, 76, 0, 32, seventyseven));
+ }
+
+ private void TestFromString(string s, int precision, int scale,
int byteWidth, string result)
+ {
+ var bytes = new byte[byteWidth];
+ DecimalUtility.GetBytes(s, precision, scale, byteWidth, bytes);
+ Assert.Equal(result, DecimalUtility.GetString(new
ArrowBuffer(bytes), 0, precision, scale, byteWidth));
+ }
+
+ [Theory]
+ [InlineData("", 10, 2, 16, typeof(ArgumentException))]
+ [InlineData("", 10, 2, 32, typeof(ArgumentException))]
+ [InlineData(null, 10, 2, 32, typeof(ArgumentException))]
+ [InlineData("1.23", 10, 1, 16, typeof(OverflowException))]
+ [InlineData("12345678901234567890", 24, 1, 8,
typeof(OverflowException))]
+ [InlineData("abc", 24, 1, 8, typeof(ArgumentException))]
+ public void ParseErrors(string s, int precision, int scale, int
byteWidth, Type exceptionType)
+ {
+ byte[] bytes = new byte[byteWidth];
+ Assert.Throws(exceptionType, () => DecimalUtility.GetBytes(s,
precision, scale, byteWidth, bytes));
+ }
+ }
}
}