Skip to content

Commit

Permalink
Merge pull request #126 from josefblaha/issues/#125-overflow-2
Browse files Browse the repository at this point in the history
Improved arithmetic overflow detection to fix #125
  • Loading branch information
FlorianRappl authored Sep 5, 2024
2 parents 6ecbdc8 + 0865ef2 commit a055a82
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 10 deletions.
68 changes: 68 additions & 0 deletions src/Mages.Core.Tests/NumberHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Mages.Core.Tokens;
using NUnit.Framework;

namespace Mages.Core.Tests
{
[TestFixture]
public class NumberHelperTests
{
[TestCase(0u, 0u)]
[TestCase(1u, 0u)]
[TestCase(0u, 1u)]
[TestCase(1u, 1u)]
[TestCase(123u, 123u)]
[TestCase(20u, 123u)]
[TestCase(10u, ulong.MaxValue / 10)]
[TestCase(0u, ulong.MaxValue)]
public void TryMultiply_happy_case(ulong x, ulong y)
{
var result = NumberHelper.TryMultiply(x, y, out var actual);
Assert.AreEqual(checked(x * y), actual);
Assert.IsTrue(result);
}

[TestCase(ulong.MaxValue, 2u)]
[TestCase(ulong.MaxValue, ulong.MaxValue)]
[TestCase(ulong.MaxValue / 2 + 1, 3u)]
[TestCase(ulong.MaxValue / 3 + 1, 4u)]
[TestCase(ulong.MaxValue - 1, 2u)]
[TestCase(ulong.MaxValue / 10 * 9 + 1, 2u)]
[TestCase(0xFFFFFFFFFFFFFFFFu, 2u)]
[TestCase(8409014139716477191u, 10u)]
public void TryMultiply_overflow_case(ulong x, ulong y)
{
var result = NumberHelper.TryMultiply(x, y, out var actual);
Assert.AreEqual(0, actual);
Assert.IsFalse(result);
}

[TestCase(0u, 0u)]
[TestCase(1u, 0u)]
[TestCase(0u, 1u)]
[TestCase(1u, 1u)]
[TestCase(123u, 123u)]
[TestCase(20u, 123u)]
[TestCase(0u, ulong.MaxValue)]
[TestCase(ulong.MaxValue, 0u)]
[TestCase(ulong.MaxValue - 10, 10u)]
public void TryAdd_happy_case(ulong x, ulong y)
{
var result = NumberHelper.TryAdd(x, y, out var actual);
Assert.AreEqual(checked(x + y), actual);
Assert.IsTrue(result);
}

[TestCase(1u, ulong.MaxValue)]
[TestCase(ulong.MaxValue, 1u)]
[TestCase(ulong.MaxValue, ulong.MaxValue)]
[TestCase(ulong.MaxValue / 2 + 1, ulong.MaxValue / 2 + 2)]
[TestCase(ulong.MaxValue - 1, 2u)]
[TestCase(0xFFFFFFFFFFFFFFFFu, 1u)]
public void TryAdd_overflow_case(ulong x, ulong y)
{
var result = NumberHelper.TryAdd(x, y, out var actual);
Assert.AreEqual(0, actual);
Assert.IsFalse(result);
}
}
}
13 changes: 13 additions & 0 deletions src/Mages.Core.Tests/NumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ public void NumberScannerLargeValue()
Assert.AreEqual(9223372036854776000.0, ((NumberToken)result).Value);
}

[TestCase("0.8409014139716477191")]
[TestCase("0.84090141397164771912")]
public void NumberScannerLargePrecisionValue(string source)
{
var scanner = new StringScanner(source);
Assert.IsTrue(scanner.MoveNext());
var tokenizer = new NumberTokenizer();
var result = tokenizer.Next(scanner);
Assert.IsInstanceOf<NumberToken>(result);
// double has a precision of ~15-17 digits
Assert.AreEqual(0.840901413971647, ((NumberToken)result).Value, 1e-15);
}

[Test]
public void NumberScannerScientificPlus()
{
Expand Down
49 changes: 49 additions & 0 deletions src/Mages.Core/Tokens/NumberHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace Mages.Core.Tokens;

/// <summary>
/// Helper methods for number arithmetics.
/// </summary>
internal static class NumberHelper
{
/// <summary>
/// Multiplies two unsigned numbers if their product does not exceed
/// <see cref="ulong.MaxValue"/>.
/// </summary>
/// <returns>A value indicating whether the numbers were multiplied.</returns>
public static bool TryMultiply(ulong x, ulong y, out ulong product)
{
if (x == 0 || y == 0)
{
product = 0;
return true;
}

// Check for overflow
if (x > ulong.MaxValue / y)
{
product = 0;
return false;
}

product = x * y;
return true;
}

/// <summary>
/// Adds two unsigned numbers if their sum does not exceed
/// <see cref="ulong.MaxValue"/>.
/// </summary>
/// <returns>A value indicating whether the numbers were added.</returns>
public static bool TryAdd(ulong x, ulong y, out ulong sum)
{
// Check for overflow
if (x > ulong.MaxValue - y)
{
sum = 0;
return false;
}

sum = x + y;
return true;
}
}
17 changes: 7 additions & 10 deletions src/Mages.Core/Tokens/NumberTokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,18 +258,15 @@ private void AddValue(UInt64 scale, UInt64 diff)
{
_shifts++;
}
else if (NumberHelper.TryMultiply(_value, scale, out var product) && NumberHelper.TryAdd(product, diff, out var newValue))
{
// assign new value
_value = newValue;
}
else
{
var newValue = _value * scale + diff;

if (newValue < _value)
{
_shifts++;
}
else
{
_value = newValue;
}
// overflow!
_shifts++;
}
}
}
Expand Down

0 comments on commit a055a82

Please sign in to comment.