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-dotnet.git
The following commit(s) were added to refs/heads/main by this push:
new 009762e Improvements to decimal conversion (#292)
009762e is described below
commit 009762ed78eab160c1b31774e4db6480a5ae5f80
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Wed Mar 25 10:46:44 2026 -0700
Improvements to decimal conversion (#292)
## What's Changed
- Fixes DecimalUtility.GetDecimal to correctly handle high-scale values
(e.g., Decimal256 with precision 76, scale 38) that previously threw
OverflowException — addresses issue #247
- Adds a fast path using Int128 on .NET 7+ that avoids BigInteger
allocation for values that fit in 128 bits
- Replaces the old fractional-part overflow check (which threw) with
FractionToDecimal, which gracefully reduces precision to fit within
decimal's ~28-digit mantissa
Closes #247.
Partially addresses #96.
---
.gitignore | 2 +
src/Apache.Arrow/Arrays/Decimal128Array.cs | 34 +++++
src/Apache.Arrow/Arrays/Decimal256Array.cs | 33 +++++
src/Apache.Arrow/DecimalUtility.cs | 159 +++++++++++++++++++--
.../DecimalArrayBenchmark.cs | 131 +++++++++++++++++
test/Apache.Arrow.Tests/Decimal128ArrayTests.cs | 77 ++++++++++
test/Apache.Arrow.Tests/Decimal256ArrayTests.cs | 97 +++++++++++++
test/Apache.Arrow.Tests/DecimalUtilityTests.cs | 2 +-
8 files changed, 521 insertions(+), 14 deletions(-)
diff --git a/.gitignore b/.gitignore
index 0ec0753..f0bff0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -282,3 +282,5 @@ artifacts/
# add .sln files back because they are ignored by the root .gitignore file
!*.sln
+
+BenchmarkDotNet.Artifacts/
diff --git a/src/Apache.Arrow/Arrays/Decimal128Array.cs
b/src/Apache.Arrow/Arrays/Decimal128Array.cs
index 2fb6f4c..15924d7 100644
--- a/src/Apache.Arrow/Arrays/Decimal128Array.cs
+++ b/src/Apache.Arrow/Arrays/Decimal128Array.cs
@@ -138,6 +138,11 @@ namespace Apache.Arrow
public int Precision => ((Decimal128Type)Data.DataType).Precision;
public int ByteWidth => ((Decimal128Type)Data.DataType).ByteWidth;
+ /// <summary>
+ /// Gets the decimal value at the index of the array. May throw an
exception if the value can't be
+ /// expressed as a <see cref="System.Decimal "/>. See <see
cref="TryGetValue(int, out decimal?)" /> for
+ /// details.
+ /// </summary>
public decimal? GetValue(int index)
{
if (IsNull(index))
@@ -147,6 +152,35 @@ namespace Apache.Arrow
return DecimalUtility.GetDecimal(ValueBuffer, Offset + index,
Scale, ByteWidth);
}
+ /// <summary>
+ /// Gets the decimal value at the index of the array. Returns false if
the value can't be
+ /// expressed as a <see cref="System.Decimal "/>. <see
cref="System.Decimal "/> is a 128-bit
+ /// floating point value with a 5 bit base-10 exponent and a 96-bit
base-10 mantissa. Decimal128
+ /// is a fixed point 128 bit value where up to 128 bits can be used
for the mantissa, and both
+ /// the number of bits and the exponent are determined by the array's
type (which is out-of-band).
+ /// This means that a <see cref="Decimal128Type"/> whose precision
minus scale is greater than 28
+ /// might produce an overflow when stored as a .NET decimal. It may
also cause rounding for
+ /// precisions greater than 28. These will silently succeed. By
contrast, <see cref="SqlDecimal"/>
+ /// can store a decimal128 value with full fidelity.
+ /// </summary>
+ public bool TryGetValue(int index, out decimal? value)
+ {
+ if (IsNull(index))
+ {
+ value = null;
+ return true;
+ }
+
+ if (DecimalUtility.TryGetDecimal(ValueBuffer, Offset + index,
Scale, ByteWidth, out decimal result))
+ {
+ value = result;
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+
public IList<decimal?> ToList(bool includeNulls = false)
{
var list = new List<decimal?>(Length);
diff --git a/src/Apache.Arrow/Arrays/Decimal256Array.cs
b/src/Apache.Arrow/Arrays/Decimal256Array.cs
index 52bfb9e..39b36c1 100644
--- a/src/Apache.Arrow/Arrays/Decimal256Array.cs
+++ b/src/Apache.Arrow/Arrays/Decimal256Array.cs
@@ -146,6 +146,11 @@ namespace Apache.Arrow
public int Precision => ((Decimal256Type)Data.DataType).Precision;
public int ByteWidth => ((Decimal256Type)Data.DataType).ByteWidth;
+ /// <summary>
+ /// Gets the decimal value at the index of the array. May throw an
exception if the value can't be
+ /// expressed as a <see cref="System.Decimal "/>. See <see
cref="TryGetValue(int, out decimal?)" /> for
+ /// details.
+ /// </summary>
public decimal? GetValue(int index)
{
if (IsNull(index))
@@ -156,6 +161,34 @@ namespace Apache.Arrow
return DecimalUtility.GetDecimal(ValueBuffer, Offset + index,
Scale, ByteWidth);
}
+ /// <summary>
+ /// Gets the decimal value at the index of the array. Returns false if
the value can't be
+ /// expressed as a <see cref="System.Decimal "/>. <see
cref="System.Decimal "/> is a 128-bit
+ /// floating point value with a 5 bit base-10 exponent and a 96-bit
base-10 mantissa. Decimal256
+ /// is a fixed point 256 bit value where up to 256 bits can be used
for the mantissa, and both
+ /// the number of bits and the exponent are determined by the array's
type (which is out-of-band).
+ /// This means that a <see cref="Decimal256Type"/> whose precision
minus scale is greater than 28
+ /// might produce an overflow when stored as a .NET decimal. It may
also cause rounding for
+ /// precisions greater than 28. These will silently succeed.
+ /// </summary>
+ public bool TryGetValue(int index, out decimal? value)
+ {
+ if (IsNull(index))
+ {
+ value = null;
+ return true;
+ }
+
+ if (DecimalUtility.TryGetDecimal(ValueBuffer, Offset + index,
Scale, ByteWidth, out decimal result))
+ {
+ value = result;
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
+
public IList<decimal?> ToList(bool includeNulls = false)
{
var list = new List<decimal?>(Length);
diff --git a/src/Apache.Arrow/DecimalUtility.cs
b/src/Apache.Arrow/DecimalUtility.cs
index c4e7256..968894a 100644
--- a/src/Apache.Arrow/DecimalUtility.cs
+++ b/src/Apache.Arrow/DecimalUtility.cs
@@ -14,6 +14,9 @@
// limitations under the License.
using System;
+#if NET7_0_OR_GREATER
+using System.Buffers.Binary;
+#endif
using System.Data.SqlTypes;
using System.Numerics;
@@ -35,10 +38,131 @@ namespace Apache.Arrow
private static int PowersOfTenLength => s_powersOfTen.Length - 1;
+#if NET7_0_OR_GREATER
+ // decimal mantissa is 96 bits unsigned
+ private static readonly UInt128 s_maxDecimalMantissa = new
UInt128(0x0000_0000_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF);
+
+ private static readonly UInt128[] s_uint128PowersOfTen =
ComputeUInt128Powers();
+
+ private static UInt128[] ComputeUInt128Powers()
+ {
+ var powers = new UInt128[39]; // 10^0 through 10^38
+ powers[0] = 1;
+ for (int i = 1; i < powers.Length; i++)
+ powers[i] = powers[i - 1] * 10;
+ return powers;
+ }
+#endif
+
internal static decimal GetDecimal(in ArrowBuffer valueBuffer, int
index, int scale, int byteWidth)
+ {
+ if (!TryGetDecimal(valueBuffer, index, scale, byteWidth, out
decimal result))
+ {
+ throw new OverflowException("Value is too large or too small
to be represented as a decimal");
+ }
+ return result;
+ }
+
+ internal static bool TryGetDecimal(in ArrowBuffer valueBuffer, int
index, int scale, int byteWidth, out decimal result)
{
int startIndex = index * byteWidth;
ReadOnlySpan<byte> value = valueBuffer.Span.Slice(startIndex,
byteWidth);
+
+#if NET7_0_OR_GREATER
+ if (byteWidth == 16)
+ {
+ Int128 int128Value =
BinaryPrimitives.ReadInt128LittleEndian(value);
+ return TryGetDecimalViaInt128(int128Value, scale, out result);
+ }
+
+ if (byteWidth == 32)
+ {
+ // Check if the value fits in 128 bits (upper 128 bits are
sign extension)
+ Int128 lower = BinaryPrimitives.ReadInt128LittleEndian(value);
+ Int128 upper =
BinaryPrimitives.ReadInt128LittleEndian(value.Slice(16));
+ Int128 signExtension = lower < 0 ? Int128.NegativeOne :
Int128.Zero;
+ if (upper == signExtension)
+ {
+ return TryGetDecimalViaInt128(lower, scale, out result);
+ }
+ }
+#endif
+
+ return TryGetDecimalViaBigInteger(value, scale, out result);
+ }
+
+#if NET7_0_OR_GREATER
+ private static bool TryGetDecimalViaInt128(Int128 integerValue, int
scale, out decimal result)
+ {
+ bool negative = integerValue < 0;
+ UInt128 abs = negative ? (UInt128)(-integerValue) :
(UInt128)integerValue;
+
+ // Fast path: value fits directly in decimal (96-bit mantissa,
scale <= 28)
+ if (abs <= s_maxDecimalMantissa && scale <= 28)
+ {
+ result = UInt128ToDecimal(abs, negative, (byte)scale);
+ return true;
+ }
+
+ if (scale == 0)
+ {
+ result = default;
+ return false;
+ }
+
+ // Split into integer and fractional parts
+ if (scale <= 38)
+ {
+ UInt128 scaleBy = s_uint128PowersOfTen[scale];
+ (UInt128 intPart, UInt128 fracPart) = UInt128.DivRem(abs,
scaleBy);
+
+ if (intPart > s_maxDecimalMantissa)
+ {
+ result = default;
+ return false;
+ }
+
+ decimal intDecimal = UInt128ToDecimal(intPart, negative, 0);
+
+ // Reduce fractional part to fit decimal constraints
+ int fracScale = scale;
+ while (fracPart > s_maxDecimalMantissa || fracScale > 28)
+ {
+ fracPart /= 10;
+ fracScale--;
+ }
+
+ decimal fracDecimal = UInt128ToDecimal(fracPart, false,
(byte)fracScale);
+ result = negative ? intDecimal - fracDecimal : intDecimal +
fracDecimal;
+ return true;
+ }
+ else
+ {
+ // scale > 38: abs < 2^127 < 10^39, so the integer part is 0
or very small.
+ // Reduce mantissa and scale together until they fit decimal
constraints.
+ UInt128 mantissa = abs;
+ int decScale = scale;
+ while (mantissa > s_maxDecimalMantissa || decScale > 28)
+ {
+ mantissa /= 10;
+ decScale--;
+ }
+
+ result = UInt128ToDecimal(mantissa, negative, (byte)decScale);
+ return true;
+ }
+ }
+
+ private static decimal UInt128ToDecimal(UInt128 value, bool negative,
byte scale)
+ {
+ ulong lo64 = (ulong)(value & ulong.MaxValue);
+ uint hi32 = (uint)(value >> 64);
+ return new decimal((int)lo64, (int)(lo64 >> 32), (int)hi32,
negative, scale);
+ }
+#endif
+
+ private static bool TryGetDecimalViaBigInteger(ReadOnlySpan<byte>
value, int scale, out decimal result)
+ {
BigInteger integerValue;
#if NETCOREAPP
@@ -52,25 +176,19 @@ namespace Apache.Arrow
BigInteger scaleBy = BigInteger.Pow(10, scale);
BigInteger integerPart = BigInteger.DivRem(integerValue,
scaleBy, out BigInteger fractionalPart);
- // decimal overflow, not much we can do here - C# needs a
BigDecimal
- if (integerPart > _maxDecimal)
- {
- throw new OverflowException($"Value: {integerPart} of
{integerValue} is too big be represented as a decimal");
- }
- else if (integerPart < _minDecimal)
+ if (integerPart > _maxDecimal || integerPart < _minDecimal)
{
- throw new OverflowException($"Value: {integerPart} of
{integerValue} is too small be represented as a decimal");
- }
- else if (fractionalPart > _maxDecimal || fractionalPart <
_minDecimal)
- {
- throw new OverflowException($"Value: {fractionalPart} of
{integerValue} is too precise be represented as a decimal");
+ result = default;
+ return false;
}
- return (decimal)integerPart + DivideByScale(fractionalPart,
scale);
+ result = (decimal)integerPart +
FractionToDecimal(fractionalPart, scale);
+ return true;
}
else
{
- return DivideByScale(integerValue, scale);
+ result = DivideByScale(integerValue, scale);
+ return true;
}
}
@@ -205,6 +323,21 @@ namespace Apache.Arrow
}
}
+ private static decimal FractionToDecimal(BigInteger fractionalPart,
int scale)
+ {
+ // The fractional BigInteger may have more digits than decimal can
represent (~28-29).
+ // Reduce it by dividing out powers of 10, losing the
least-significant digits,
+ // then divide the remainder by the reduced scale.
+ int digitsRemoved = 0;
+ while (fractionalPart > _maxDecimal || fractionalPart <
_minDecimal)
+ {
+ fractionalPart /= 10;
+ digitsRemoved++;
+ }
+
+ return DivideByScale(fractionalPart, scale - digitsRemoved);
+ }
+
private static decimal DivideByScale(BigInteger integerValue, int
scale)
{
decimal result = (decimal)integerValue; // this cast is safe here
diff --git a/test/Apache.Arrow.Benchmarks/DecimalArrayBenchmark.cs
b/test/Apache.Arrow.Benchmarks/DecimalArrayBenchmark.cs
new file mode 100644
index 0000000..a3089a1
--- /dev/null
+++ b/test/Apache.Arrow.Benchmarks/DecimalArrayBenchmark.cs
@@ -0,0 +1,131 @@
+// 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.Data.SqlTypes;
+using Apache.Arrow.Types;
+using BenchmarkDotNet.Attributes;
+
+namespace Apache.Arrow.Benchmarks
+{
+ [MemoryDiagnoser]
+ public class DecimalArrayBenchmark
+ {
+ [Params(10_000)]
+ public int Count { get; set; }
+
+ private Decimal128Array _decimal128LowScale;
+ private Decimal128Array _decimal128HighScale;
+ private Decimal256Array _decimal256LowScale;
+ private Decimal256Array _decimal256HighScale;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ var random = new Random(42);
+
+ _decimal128LowScale = BuildDecimal128Array(new Decimal128Type(14,
4), random);
+ _decimal128HighScale = BuildDecimal128Array(new Decimal128Type(38,
20), random);
+ _decimal256LowScale = BuildDecimal256Array(new Decimal256Type(14,
4), random);
+ _decimal256HighScale = BuildDecimal256Array(new Decimal256Type(76,
38), random);
+ }
+
+ private Decimal128Array BuildDecimal128Array(Decimal128Type type,
Random random)
+ {
+ var builder = new Decimal128Array.Builder(type);
+ for (int i = 0; i < Count; i++)
+ {
+ builder.Append((decimal)Math.Round(random.NextDouble() *
10000, Math.Min(type.Scale, 10)));
+ }
+ return builder.Build();
+ }
+
+ private Decimal256Array BuildDecimal256Array(Decimal256Type type,
Random random)
+ {
+ var builder = new Decimal256Array.Builder(type);
+ for (int i = 0; i < Count; i++)
+ {
+ builder.Append((decimal)Math.Round(random.NextDouble() *
10000, Math.Min(type.Scale, 10)));
+ }
+ return builder.Build();
+ }
+
+ [Benchmark]
+ public decimal? Decimal128_GetValue_LowScale()
+ {
+ decimal? sum = 0;
+ for (int i = 0; i < _decimal128LowScale.Length; i++)
+ {
+ sum += _decimal128LowScale.GetValue(i);
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public decimal? Decimal128_GetValue_HighScale()
+ {
+ decimal? sum = 0;
+ for (int i = 0; i < _decimal128HighScale.Length; i++)
+ {
+ sum += _decimal128HighScale.GetValue(i);
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public decimal? Decimal256_GetValue_LowScale()
+ {
+ decimal? sum = 0;
+ for (int i = 0; i < _decimal256LowScale.Length; i++)
+ {
+ sum += _decimal256LowScale.GetValue(i);
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public decimal? Decimal256_GetValue_HighScale()
+ {
+ decimal? sum = 0;
+ for (int i = 0; i < _decimal256HighScale.Length; i++)
+ {
+ sum += _decimal256HighScale.GetValue(i);
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public SqlDecimal? Decimal128_GetSqlDecimal()
+ {
+ SqlDecimal? sum = 0;
+ for (int i = 0; i < _decimal128LowScale.Length; i++)
+ {
+ sum += _decimal128LowScale.GetSqlDecimal(i);
+ }
+ return sum;
+ }
+
+ [Benchmark]
+ public string Decimal256_GetString_HighScale()
+ {
+ string last = null;
+ for (int i = 0; i < _decimal256HighScale.Length; i++)
+ {
+ last = _decimal256HighScale.GetString(i);
+ }
+ return last;
+ }
+ }
+}
diff --git a/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
b/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
index c5e0647..0ea6326 100644
--- a/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
+++ b/test/Apache.Arrow.Tests/Decimal128ArrayTests.cs
@@ -459,6 +459,83 @@ namespace Apache.Arrow.Tests
}
}
+ public class HighPrecisionGetValue
+ {
+ [Fact]
+ public void GetValueWithHighScale()
+ {
+ // Decimal128 supports up to precision 38, scale 38
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
20))
+ .Append(2422.85527600000m)
+ .Build();
+
+ Assert.Equal(2422.85527600000m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithMaxScaleFractionalOnly()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
30))
+ .Append(0.12345678m)
+ .Build();
+
+ Assert.Equal(0.12345678m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithHighScaleNegative()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
20))
+ .Append(-2422.85527600000m)
+ .Build();
+
+ Assert.Equal(-2422.85527600000m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void TryGetValueReturnsTrue()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
20))
+ .Append(2422.85527600000m)
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Equal(2422.85527600000m, value);
+ }
+
+ [Fact]
+ public void TryGetValueReturnsFalse()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
4))
+
.Append(SqlDecimal.Parse("100000000000000000000000000000000"))
+ .Build();
+
+ Assert.False(array.TryGetValue(0, out decimal? value));
+ }
+
+ [Fact]
+ public void TryGetValueCanRound()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
8))
+
.Append(SqlDecimal.Parse("10000000000000000000000000000.99"))
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Equal(10000000000000000000000000001m, value);
+ }
+
+ [Fact]
+ public void TryGetValueNullReturnsTrue()
+ {
+ var array = new Decimal128Array.Builder(new Decimal128Type(38,
20))
+ .AppendNull()
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Null(value);
+ }
+ }
+
[Fact]
public void SliceDecimal128Array()
{
diff --git a/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
b/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
index 1c94916..a604edf 100644
--- a/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
+++ b/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs
@@ -477,6 +477,103 @@ namespace Apache.Arrow.Tests
}
}
+ public class HighPrecisionGetValue
+ {
+ [Fact]
+ public void GetValueWithHighPrecisionAndScale()
+ {
+ // Exact scenario from
https://github.com/apache/arrow-dotnet/issues/247
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(2422.85527600000m)
+ .Build();
+
+ Assert.Equal(2422.85527600000m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithHighScaleFractionalOnly()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(0.12345678901234567890m)
+ .Build();
+
+ Assert.Equal(0.12345678901234567890m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithHighScaleNegative()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(-2422.85527600000m)
+ .Build();
+
+ Assert.Equal(-2422.85527600000m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithHighScaleZero()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(0m)
+ .Build();
+
+ Assert.Equal(0m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void GetValueWithHighScaleWholeNumber()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(12345m)
+ .Build();
+
+ Assert.Equal(12345m, array.GetValue(0));
+ }
+
+ [Fact]
+ public void TryGetValueReturnsTrue()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .Append(2422.85527600000m)
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Equal(2422.85527600000m, value);
+ }
+
+ [Fact]
+ public void TryGetValueReturnsFalse()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(38,
4))
+
.Append(SqlDecimal.Parse("100000000000000000000000000000000"))
+ .Build();
+
+ Assert.False(array.TryGetValue(0, out decimal? value));
+ }
+
+ [Fact]
+ public void TryGetValueCanRound()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(38,
8))
+
.Append(SqlDecimal.Parse("10000000000000000000000000000.99"))
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Equal(10000000000000000000000000001m, value);
+ }
+
+ [Fact]
+ public void TryGetValueNullReturnsTrue()
+ {
+ var array = new Decimal256Array.Builder(new Decimal256Type(76,
38))
+ .AppendNull()
+ .Build();
+
+ Assert.True(array.TryGetValue(0, out decimal? value));
+ Assert.Null(value);
+ }
+ }
+
[Fact]
public void SliceDecimal256Array()
{
diff --git a/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
b/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
index 1156ecb..7f2b3ef 100644
--- a/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
+++ b/test/Apache.Arrow.Tests/DecimalUtilityTests.cs
@@ -50,7 +50,7 @@ namespace Apache.Arrow.Tests
[Theory]
[InlineData(4.56, 38, 9, false)]
- [InlineData(7.89, 76, 38, true)]
+ [InlineData(7.89, 76, 38, false)]
public void Decimal256HasExpectedResultOrThrows(decimal d, int
precision, int scale, bool shouldThrow)
{
var builder = new Decimal256Array.Builder(new
Decimal256Type(precision, scale));