Skip to content

Commit a055a82

Browse files
authored
Merge pull request #126 from josefblaha/issues/#125-overflow-2
Improved arithmetic overflow detection to fix #125
2 parents 6ecbdc8 + 0865ef2 commit a055a82

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Mages.Core.Tokens;
2+
using NUnit.Framework;
3+
4+
namespace Mages.Core.Tests
5+
{
6+
[TestFixture]
7+
public class NumberHelperTests
8+
{
9+
[TestCase(0u, 0u)]
10+
[TestCase(1u, 0u)]
11+
[TestCase(0u, 1u)]
12+
[TestCase(1u, 1u)]
13+
[TestCase(123u, 123u)]
14+
[TestCase(20u, 123u)]
15+
[TestCase(10u, ulong.MaxValue / 10)]
16+
[TestCase(0u, ulong.MaxValue)]
17+
public void TryMultiply_happy_case(ulong x, ulong y)
18+
{
19+
var result = NumberHelper.TryMultiply(x, y, out var actual);
20+
Assert.AreEqual(checked(x * y), actual);
21+
Assert.IsTrue(result);
22+
}
23+
24+
[TestCase(ulong.MaxValue, 2u)]
25+
[TestCase(ulong.MaxValue, ulong.MaxValue)]
26+
[TestCase(ulong.MaxValue / 2 + 1, 3u)]
27+
[TestCase(ulong.MaxValue / 3 + 1, 4u)]
28+
[TestCase(ulong.MaxValue - 1, 2u)]
29+
[TestCase(ulong.MaxValue / 10 * 9 + 1, 2u)]
30+
[TestCase(0xFFFFFFFFFFFFFFFFu, 2u)]
31+
[TestCase(8409014139716477191u, 10u)]
32+
public void TryMultiply_overflow_case(ulong x, ulong y)
33+
{
34+
var result = NumberHelper.TryMultiply(x, y, out var actual);
35+
Assert.AreEqual(0, actual);
36+
Assert.IsFalse(result);
37+
}
38+
39+
[TestCase(0u, 0u)]
40+
[TestCase(1u, 0u)]
41+
[TestCase(0u, 1u)]
42+
[TestCase(1u, 1u)]
43+
[TestCase(123u, 123u)]
44+
[TestCase(20u, 123u)]
45+
[TestCase(0u, ulong.MaxValue)]
46+
[TestCase(ulong.MaxValue, 0u)]
47+
[TestCase(ulong.MaxValue - 10, 10u)]
48+
public void TryAdd_happy_case(ulong x, ulong y)
49+
{
50+
var result = NumberHelper.TryAdd(x, y, out var actual);
51+
Assert.AreEqual(checked(x + y), actual);
52+
Assert.IsTrue(result);
53+
}
54+
55+
[TestCase(1u, ulong.MaxValue)]
56+
[TestCase(ulong.MaxValue, 1u)]
57+
[TestCase(ulong.MaxValue, ulong.MaxValue)]
58+
[TestCase(ulong.MaxValue / 2 + 1, ulong.MaxValue / 2 + 2)]
59+
[TestCase(ulong.MaxValue - 1, 2u)]
60+
[TestCase(0xFFFFFFFFFFFFFFFFu, 1u)]
61+
public void TryAdd_overflow_case(ulong x, ulong y)
62+
{
63+
var result = NumberHelper.TryAdd(x, y, out var actual);
64+
Assert.AreEqual(0, actual);
65+
Assert.IsFalse(result);
66+
}
67+
}
68+
}

src/Mages.Core.Tests/NumberTests.cs

+13
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ public void NumberScannerLargeValue()
6767
Assert.AreEqual(9223372036854776000.0, ((NumberToken)result).Value);
6868
}
6969

70+
[TestCase("0.8409014139716477191")]
71+
[TestCase("0.84090141397164771912")]
72+
public void NumberScannerLargePrecisionValue(string source)
73+
{
74+
var scanner = new StringScanner(source);
75+
Assert.IsTrue(scanner.MoveNext());
76+
var tokenizer = new NumberTokenizer();
77+
var result = tokenizer.Next(scanner);
78+
Assert.IsInstanceOf<NumberToken>(result);
79+
// double has a precision of ~15-17 digits
80+
Assert.AreEqual(0.840901413971647, ((NumberToken)result).Value, 1e-15);
81+
}
82+
7083
[Test]
7184
public void NumberScannerScientificPlus()
7285
{

src/Mages.Core/Tokens/NumberHelper.cs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
namespace Mages.Core.Tokens;
2+
3+
/// <summary>
4+
/// Helper methods for number arithmetics.
5+
/// </summary>
6+
internal static class NumberHelper
7+
{
8+
/// <summary>
9+
/// Multiplies two unsigned numbers if their product does not exceed
10+
/// <see cref="ulong.MaxValue"/>.
11+
/// </summary>
12+
/// <returns>A value indicating whether the numbers were multiplied.</returns>
13+
public static bool TryMultiply(ulong x, ulong y, out ulong product)
14+
{
15+
if (x == 0 || y == 0)
16+
{
17+
product = 0;
18+
return true;
19+
}
20+
21+
// Check for overflow
22+
if (x > ulong.MaxValue / y)
23+
{
24+
product = 0;
25+
return false;
26+
}
27+
28+
product = x * y;
29+
return true;
30+
}
31+
32+
/// <summary>
33+
/// Adds two unsigned numbers if their sum does not exceed
34+
/// <see cref="ulong.MaxValue"/>.
35+
/// </summary>
36+
/// <returns>A value indicating whether the numbers were added.</returns>
37+
public static bool TryAdd(ulong x, ulong y, out ulong sum)
38+
{
39+
// Check for overflow
40+
if (x > ulong.MaxValue - y)
41+
{
42+
sum = 0;
43+
return false;
44+
}
45+
46+
sum = x + y;
47+
return true;
48+
}
49+
}

src/Mages.Core/Tokens/NumberTokenizer.cs

+7-10
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,15 @@ private void AddValue(UInt64 scale, UInt64 diff)
258258
{
259259
_shifts++;
260260
}
261+
else if (NumberHelper.TryMultiply(_value, scale, out var product) && NumberHelper.TryAdd(product, diff, out var newValue))
262+
{
263+
// assign new value
264+
_value = newValue;
265+
}
261266
else
262267
{
263-
var newValue = _value * scale + diff;
264-
265-
if (newValue < _value)
266-
{
267-
_shifts++;
268-
}
269-
else
270-
{
271-
_value = newValue;
272-
}
268+
// overflow!
269+
_shifts++;
273270
}
274271
}
275272
}

0 commit comments

Comments
 (0)