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));
+            }
+        }
     }
 }


Reply via email to