Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/FirebirdSql.Data.FirebirdClient.Tests/DecimalCodecTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* The contents of this file are subject to the Initial
* Developer's Public License Version 1.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
* https://github.com/FirebirdSQL/NETProvider/raw/master/license.txt.
*
* Software distributed under the License is distributed on
* an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
* express or implied. See the License for the specific
* language governing rights and limitations under the License.
*
* All Rights Reserved.
*/

//$Authors = F.D.Castel

using System;
using System.Numerics;
using FirebirdSql.Data.Common;
using FirebirdSql.Data.TestsBase;
using FirebirdSql.Data.Types;
using NUnit.Framework;

namespace FirebirdSql.Data.FirebirdClient.Tests;

[NoServerCategory]
public class DecimalCodecTests
{
static readonly object[] RepresentableDecFloat16Values = new object[]
{
new FbDecFloat(BigInteger.Parse("9999999999999999"), 369),
new FbDecFloat(BigInteger.Parse("-9999999999999999"), 369),
new FbDecFloat(1, -398),
new FbDecFloat(-1, -398),
// representable only after normalization
new FbDecFloat(1, 384),
new FbDecFloat(-1, 384),
new FbDecFloat(BigInteger.Parse("100000000000000000000"), -20),
new FbDecFloat(BigInteger.Parse("12345678901234560"), 0),
new FbDecFloat(100, -400),
new FbDecFloat(0, 1000),
new FbDecFloat(0, -1000),
};
[TestCaseSource(nameof(RepresentableDecFloat16Values))]
public void DecFloat16EncodesRepresentableValueExactly(FbDecFloat value)
{
var result = DecimalCodec.DecFloat16.ParseBytes(DecimalCodec.DecFloat16.EncodeDecimal(value));
Assert.AreEqual(value, result);
}

static readonly object[] RepresentableDecFloat34Values = new object[]
{
new FbDecFloat(BigInteger.Parse("9999999999999999999999999999999999"), 6111),
new FbDecFloat(1, -6176),
// representable only after normalization
new FbDecFloat(1, 6144),
new FbDecFloat(BigInteger.Parse("12345678901234567890123456789012340"), 0),
new FbDecFloat(BigInteger.Parse("-12345678901234567890123456789012340000"), -40),
new FbDecFloat(1000, -6179),
new FbDecFloat(0, int.MaxValue),
new FbDecFloat(0, int.MinValue),
};
[TestCaseSource(nameof(RepresentableDecFloat34Values))]
public void DecFloat34EncodesRepresentableValueExactly(FbDecFloat value)
{
var result = DecimalCodec.DecFloat34.ParseBytes(DecimalCodec.DecFloat34.EncodeDecimal(value));
Assert.AreEqual(value, result);
}

[Test]
public void ZeroExponentIsClamped()
{
var result = DecimalCodec.DecFloat16.ParseBytes(DecimalCodec.DecFloat16.EncodeDecimal(new FbDecFloat(0, 1000)));
Assert.AreEqual(369, result.Exponent);
}

static readonly object[] UnrepresentableDecFloat16Values = new object[]
{
new FbDecFloat(BigInteger.Parse("30000000000000004"), -17),
new FbDecFloat(BigInteger.Parse("-12345678901234567"), 0),
new FbDecFloat(1, 385),
new FbDecFloat(BigInteger.Parse("9999999999999999"), 370),
new FbDecFloat(1, -399),
new FbDecFloat(15, -400),
new FbDecFloat(1, int.MaxValue),
new FbDecFloat(1, int.MinValue),
};
[TestCaseSource(nameof(UnrepresentableDecFloat16Values))]
public void DecFloat16ThrowsInsteadOfRounding(FbDecFloat value)
{
Assert.Throws<OverflowException>(() => DecimalCodec.DecFloat16.EncodeDecimal(value));
}

static readonly object[] UnrepresentableDecFloat34Values = new object[]
{
new FbDecFloat(BigInteger.Parse("12345678901234567890123456789012345"), 0),
new FbDecFloat(1, 6145),
new FbDecFloat(1, -6177),
};
[TestCaseSource(nameof(UnrepresentableDecFloat34Values))]
public void DecFloat34ThrowsInsteadOfRounding(FbDecFloat value)
{
Assert.Throws<OverflowException>(() => DecimalCodec.DecFloat34.EncodeDecimal(value));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,25 @@ public override async Task SetUp()
new object[] { new FbDecFloat(-10, 34), "-100000000000000000000000000000000000" },
new object[] { new FbDecFloat(BigInteger.Parse("123000000001"), -10), "123.000000001E-1" },
new object[] { new FbDecFloat(BigInteger.Parse("-123000000001"), -10), "-123.000000001E-1" },
new object[] { new FbDecFloat(1, 384), "1E384" },
new object[] { new FbDecFloat(BigInteger.Parse("100000000000000000000"), -20), "1" },
new object[] { new FbDecFloat(100, -400), "1E-398" },
new object[] { (FbDecFloat)2.0, "2" },
new object[] { (FbDecFloat)1e-20, "1E-20" },
new object[] { (FbDecFloat)2.5f, "2.5" },
new object[] { (FbDecFloat)100m, "100" },
new object[] { (FbDecFloat)1.10m, "1.10" },
};

static readonly object[] UnrepresentableValues = new object[]
{
new FbDecFloat(BigInteger.Parse("30000000000000004"), -17),
new FbDecFloat(1, 385),
new FbDecFloat(1, -399),
(FbDecFloat)(0.1 + 0.2),
(FbDecFloat)decimal.MaxValue,
};

[TestCaseSource(nameof(TestValues))]
public async Task ReadsValueCorrectly(FbDecFloat value, string castValue)
{
Expand All @@ -88,6 +105,22 @@ public async Task PassesValueCorrectly(FbDecFloat value, string dummy)
}
}

[TestCaseSource(nameof(UnrepresentableValues))]
public async Task PassesUnrepresentableValueThrows(FbDecFloat value)
{
await using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select cast(@value as decfloat(16)) from rdb$database";
cmd.Parameters.AddWithValue("value", value);
Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteScalarAsync());
}
await using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select cast(1 as decfloat(16)) from rdb$database";
Assert.AreEqual((FbDecFloat)1, (FbDecFloat)await cmd.ExecuteScalarAsync(), "connection must remain usable");
}
}

[Test]
public async Task ReadsValueNullCorrectly()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,25 @@ public override async Task SetUp()
new object[] { new FbDecFloat(-10, 34), "-100000000000000000000000000000000000" },
new object[] { new FbDecFloat(BigInteger.Parse("123000000001"), -10), "123.000000001E-1" },
new object[] { new FbDecFloat(BigInteger.Parse("-123000000001"), -10), "-123.000000001E-1" },
new object[] { new FbDecFloat(1, 6144), "1E6144" },
new object[] { new FbDecFloat(BigInteger.Parse("12345678901234567890123456789012340"), 0), "12345678901234567890123456789012340" },
new object[] { new FbDecFloat(1000, -6179), "1E-6176" },
new object[] { (FbDecFloat)2.0, "2" },
new object[] { (FbDecFloat)1e-20, "1E-20" },
new object[] { (FbDecFloat)2.5f, "2.5" },
new object[] { (FbDecFloat)(0.1 + 0.2), "0.30000000000000004" },
new object[] { (FbDecFloat)100m, "100" },
new object[] { (FbDecFloat)1.10m, "1.10" },
new object[] { (FbDecFloat)decimal.MaxValue, "79228162514264337593543950335" },
};

static readonly object[] UnrepresentableValues = new object[]
{
new FbDecFloat(BigInteger.Parse("12345678901234567890123456789012345"), 0),
new FbDecFloat(1, 6145),
new FbDecFloat(1, -6177),
};

[TestCaseSource(nameof(TestValues))]
public async Task ReadsValueCorrectly(FbDecFloat value, string castValue)
{
Expand All @@ -88,6 +105,22 @@ public async Task PassesValueCorrectly(FbDecFloat value, string dummy)
}
}

[TestCaseSource(nameof(UnrepresentableValues))]
public async Task PassesUnrepresentableValueThrows(FbDecFloat value)
{
await using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select cast(@value as decfloat(34)) from rdb$database";
cmd.Parameters.AddWithValue("value", value);
Assert.ThrowsAsync<OverflowException>(() => cmd.ExecuteScalarAsync());
}
await using (var cmd = Connection.CreateCommand())
{
cmd.CommandText = "select cast(1 as decfloat(34)) from rdb$database";
Assert.AreEqual((FbDecFloat)1, (FbDecFloat)await cmd.ExecuteScalarAsync(), "connection must remain usable");
}
}

[Test]
public async Task ReadsValueNullCorrectly()
{
Expand Down
113 changes: 113 additions & 0 deletions src/FirebirdSql.Data.FirebirdClient.Tests/FbDecFloatTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

//$Authors = Jiri Cincura (jiri@cincura.net)

using System;
using System.Globalization;
using System.Numerics;
using FirebirdSql.Data.TestsBase;
using FirebirdSql.Data.Types;
Expand Down Expand Up @@ -72,4 +74,115 @@ public void EqualityFalse(FbDecFloat expected, FbDecFloat actual)
{
Assert.AreNotEqual(expected, actual);
}

static readonly object[] DecimalConversionSource = new object[]
{
new object[] { 0m, "0", 0 },
new object[] { 1m, "1", 0 },
new object[] { 100m, "100", 0 },
new object[] { -5m, "-5", 0 },
new object[] { 1.5m, "15", -1 },
new object[] { 1.10m, "110", -2 },
new object[] { -0.001m, "-1", -3 },
new object[] { decimal.MaxValue, "79228162514264337593543950335", 0 },
new object[] { decimal.MinValue, "-79228162514264337593543950335", 0 },
new object[] { 0.0000000000000000000000000001m, "1", -28 },
new object[] { -7.9228162514264337593543950335m, "-79228162514264337593543950335", -28 },
};
[TestCaseSource(nameof(DecimalConversionSource))]
public void ConvertsDecimalExactly(decimal value, string coefficient, int exponent)
{
var result = (FbDecFloat)value;
Assert.AreEqual(BigInteger.Parse(coefficient), result.Coefficient);
Assert.AreEqual(exponent, result.Exponent);
}

static readonly object[] DoubleConversionSource = new object[]
{
new object[] { 0d, "0", 0 },
new object[] { 2d, "2", 0 },
new object[] { 100d, "100", 0 },
new object[] { 0.1, "1", -1 },
new object[] { -6.6, "-66", -1 },
new object[] { 1e-20, "1", -20 },
new object[] { 1e300, "1", 300 },
new object[] { 0.1 + 0.2, "30000000000000004", -17 },
new object[] { 123456789.123456789, "12345678912345679", -8 },
new object[] { double.MaxValue, "17976931348623157", 292 },
new object[] { double.MinValue, "-17976931348623157", 292 },
new object[] { double.Epsilon, "5", -324 },
};
[TestCaseSource(nameof(DoubleConversionSource))]
public void ConvertsDoubleToShortestRoundTripValue(double value, string coefficient, int exponent)
{
var result = (FbDecFloat)value;
Assert.AreEqual(BigInteger.Parse(coefficient), result.Coefficient);
Assert.AreEqual(exponent, result.Exponent);
}

static readonly object[] SingleConversionSource = new object[]
{
new object[] { 2f, "2", 0 },
new object[] { 0.1f, "1", -1 },
new object[] { -6.6f, "-66", -1 },
new object[] { 1e-20f, "1", -20 },
new object[] { float.MaxValue, "34028235", 31 },
new object[] { float.Epsilon, "1", -45 },
};
[TestCaseSource(nameof(SingleConversionSource))]
public void ConvertsSingleToShortestRoundTripValue(float value, string coefficient, int exponent)
{
var result = (FbDecFloat)value;
Assert.AreEqual(BigInteger.Parse(coefficient), result.Coefficient);
Assert.AreEqual(exponent, result.Exponent);
}

[Test]
public void ConvertsNegativeZero()
{
Assert.AreEqual(FbDecFloat.NegativeZero, (FbDecFloat)(-0d));
Assert.AreEqual(FbDecFloat.NegativeZero, (FbDecFloat)(-0f));
Assert.AreEqual(FbDecFloat.NegativeZero, (FbDecFloat)decimal.Negate(0m));
Assert.AreNotEqual(FbDecFloat.NegativeZero, (FbDecFloat)0d);
Assert.AreNotEqual(FbDecFloat.NegativeZero, (FbDecFloat)0m);
}

[Test]
public void ConvertsSpecialValues()
{
Assert.AreEqual(FbDecFloat.PositiveNaN, (FbDecFloat)double.NaN);
Assert.AreEqual(FbDecFloat.PositiveInfinity, (FbDecFloat)double.PositiveInfinity);
Assert.AreEqual(FbDecFloat.NegativeInfinity, (FbDecFloat)double.NegativeInfinity);
Assert.AreEqual(FbDecFloat.PositiveNaN, (FbDecFloat)float.NaN);
Assert.AreEqual(FbDecFloat.PositiveInfinity, (FbDecFloat)float.PositiveInfinity);
Assert.AreEqual(FbDecFloat.NegativeInfinity, (FbDecFloat)float.NegativeInfinity);
}

[Test]
public void DoubleAndSingleConversionsRoundTrip()
{
var random = new Random(20261107);
var negativeDoubles = 0;
var negativeSingles = 0;
for (var i = 0; i < 10_000; i++)
{
// random bit patterns, so all exponents and both signs are covered
var d = BitConverter.Int64BitsToDouble(random.NextInt64(long.MinValue, long.MaxValue));
if (double.IsFinite(d))
{
var result = (FbDecFloat)d;
Assert.AreEqual(d, double.Parse(result.ToString(), CultureInfo.InvariantCulture), $"{d:R} -> {result}");
negativeDoubles += d < 0 ? 1 : 0;
}
var f = BitConverter.Int32BitsToSingle(random.Next(int.MinValue, int.MaxValue));
if (float.IsFinite(f))
{
var result = (FbDecFloat)f;
Assert.AreEqual(f, float.Parse(result.ToString(), CultureInfo.InvariantCulture), $"{f:R} -> {result}");
negativeSingles += f < 0 ? 1 : 0;
}
}
Assert.Greater(negativeDoubles, 1000);
Assert.Greater(negativeSingles, 1000);
}
}
Loading