Skip to content

Commit

Permalink
Added unit test class Issue319 for bchavez#319 with test cases decima…
Browse files Browse the repository at this point in the history
…l_with_very_large_range_succeeds to Bogus.Tests.

Updated Randomizer.Decimal to make the test pass.
  • Loading branch information
logiclrd committed Aug 14, 2020
1 parent 749aa8a commit 20e6a40
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 2 deletions.
48 changes: 48 additions & 0 deletions Source/Bogus.Tests/GitHubIssues/Issue319.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Bogus.Tests.GitHubIssues
{
public class Issue319 : SeededTest
{
class TestDataProvider : DataAttribute
{
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
yield return new object[] { 0m, decimal.MaxValue };
yield return new object[] { decimal.MinValue, decimal.MaxValue };
yield return new object[] { decimal.MinValue, 0m };
// A range whose size exceeds decimal.MaxValue but which doesn't have decimal.MinValue or decimal.MaxValue as a bound.
yield return new object[] { decimal.MinValue * 0.6m, decimal.MaxValue * 0.6m };
}
}

ITestOutputHelper _output;

public Issue319(ITestOutputHelper output)
=> _output = output;

[Theory, TestDataProvider]
public void decimal_with_very_large_range_succeeds(decimal min, decimal max)
{
var randomizer = new Randomizer();

for (int iteration = 0; iteration < 300; iteration++)
{
try
{
randomizer.Decimal(min, max).Should().BeInRange(min, max);
}
catch
{
_output.WriteLine("Test failed on iteration {0}", iteration);
throw;
}
}
}
}
}
52 changes: 50 additions & 2 deletions Source/Bogus/Randomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ public double Double(double min = 0.0d, double max = 1.0d)
/// <param name="max">Maximum, default 1.0</param>
public decimal Decimal(decimal min = 0.0m, decimal max = 1.0m)
{
if (min > max)
{
decimal tmp = min;

min = max;
max = tmp;
}

// Decimal: 128 bits wide
// bit 0: sign bit
// bit 1-10: not used
Expand Down Expand Up @@ -221,10 +229,50 @@ public decimal Decimal(decimal min = 0.0m, decimal max = 1.0m)

decimal result = new decimal(lowBits, middleBits, highBits, isNegative: false, Scale);

// Step 2: Scale the value and adjust it to the desired range. This may decrease
// Step 2: Figure out how much of the scale we can keep without causing an overflow.
// Note that the range can actually exceed decimal.MaxValue, e.g. if max is itself
// decimal.MaxValue and min is negative. So, we work with half the range, using this
// scale factor that is as close to 0.5 as possible without causing the result of
// decimal.MaxValue * ScaleFactor to round up. If it rounds up, then the result of
// decimal.MaxValue * ScaleFactor - decimal.MinValue * ScaleFactor will still be
// larger than decimal.MaxValue.
const decimal OneHalfScaleFactor = 0.4999999999999999999999999999m;

decimal halfRange = max * OneHalfScaleFactor - min * OneHalfScaleFactor;

// Two reasons we're forced to use a scaled multiplier:
//
// 1. The range (max - min) is itself too large to store in decimal.MaxValue.
// 2. The result of result * (max - min) is too large to store in decimal.MaxValue.
//
// Check condition 1:
bool useScaledMultiplier = (halfRange >= decimal.MaxValue * OneHalfScaleFactor);

decimal multiplier = halfRange;
decimal divisor = 3.9614081257132168796771975168m;

// Check condition 2:
if (result >= 1.0m)
{
decimal maximumMultiplier = decimal.MaxValue / result;

while (multiplier >= maximumMultiplier)
{
// Drop one digit of precision and try again.
multiplier *= 0.1m;
divisor *= 10m;

useScaledMultiplier = true;
}
}

// Step 3: Scale the value and adjust it to the desired range. This may decrease
// the accuracy by adjusting the scale as necessary, but we get the best possible
// outcome by starting with the most precise scale.
return result * (max - min) / 7.9228162514264337593543950335m + min;
if (useScaledMultiplier)
return result * multiplier / divisor + min;
else
return result * (max - min) / 7.9228162514264337593543950335m + min;
}

/// <summary>
Expand Down

0 comments on commit 20e6a40

Please sign in to comment.