Skip to content

Commit

Permalink
Reverts Random.Decimal() implementation from PR #300 to previous v29 …
Browse files Browse the repository at this point in the history
…implementation. #319 #320
  • Loading branch information
bchavez committed Aug 15, 2020
1 parent 749aa8a commit c5e697e
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 34 deletions.
9 changes: 7 additions & 2 deletions HISTORY.md
@@ -1,10 +1,15 @@
## v30.0.4
Release Date: 2020-08-15

* Issue 319: The `Random.Decimal()` implementation reverted to previous v29 implementation. Avoids `System.OverflowException` when calling `Random.Decimal(0, decimal.MaxValue)`. The v30 implementation moved to `Bogus.Extensions` namespace as `Random.Decimal2()` which can generate more decimal precision.

## v30.0.3
Release Date: 2020-08-13
Release Date: 2020-08-13, UNPUBLISHED FROM NUGET

* Added `f.Address.CountryOfUnitedKingdom()` extension method in `Bogus.Extensions.UnitedKingdom`.

## v30.0.2
Release Date: 2020-08-05
Release Date: 2020-08-05, UNPUBLISHED FROM NUGET

* Deterministic sequences may have changed.
* Promoted v30.0.1-beta-4 to v30.0.2 release.
Expand Down
37 changes: 37 additions & 0 deletions Source/Bogus.Tests/GitHubIssues/Issue319.cs
@@ -0,0 +1,37 @@
using System;
using Bogus.Extensions;
using FluentAssertions;
using Xunit;

namespace Bogus.Tests.GitHubIssues
{
public class Issue319 : SeededTest
{
[Fact]
public void can_generate_decimal_edge_case()
{
var r = new Randomizer();

Action a = () =>
{
r.Decimal(0m, decimal.MaxValue);
r.Decimal(0m, decimal.MaxValue);
r.Decimal(0m, decimal.MaxValue);
r.Decimal(0m, decimal.MaxValue);
};
a.Should().NotThrow();
}

[Fact]
public void decimal2_should_throw_on_edge_case()
{
var r = new Randomizer();
Action a = () =>
{
r.Decimal2(0, decimal.MaxValue);
};

a.Should().Throw<OverflowException>();
}
}
}
2 changes: 1 addition & 1 deletion Source/Bogus.Tests/RandomizerTest.cs
Expand Up @@ -194,7 +194,7 @@ public void generate_double_with_min_and_max()
[Fact]
public void generate_decimal_with_min_and_max()
{
r.Decimal(2.2m, 5.2m).Should().Be(3.8697355489728032005903907232m);
r.Decimal(2.2m, 5.2m).Should().Be(4.0105668499183690m);
}

[Fact]
Expand Down
45 changes: 45 additions & 0 deletions Source/Bogus/Extensions/ExtensionsForRandomizer.cs
@@ -0,0 +1,45 @@
namespace Bogus.Extensions
{
public static class ExtensionsForRandomizer
{
/// <summary>
/// Get a random decimal, between 0.0 and 1.0.
/// </summary>
/// <param name="min">Minimum, default 0.0</param>
/// <param name="max">Maximum, default 1.0</param>
public static decimal Decimal2(this Randomizer r, decimal min = 0.0m, decimal max = 1.0m)
{
// Decimal: 128 bits wide
// bit 0: sign bit
// bit 1-10: not used
// bit 11-15: scale (values 29, 30, 31 not used)
// bit 16-31: not used
// bit 32-127: mantissa (96 bits)

// Max value: 00000000 FFFFFFFF FFFFFFFF FFFFFFFF
// = 79228162514264337593543950335

// Max value with max scaling: 001C0000 FFFFFFFF FFFFFFFF FFFFFFFF
// = 7.9228162514264337593543950335

// Step 1: Generate a value with uniform distribution between 0 and this value.
// This ensures the greatest level of precision in the distribution of bits;
// the resulting value, after it is adjusted into the caller's desired range,
// should not skip any possible values at the least significant end of the
// mantissa.

int lowBits = r.Number(int.MinValue, int.MaxValue);
int middleBits = r.Number(int.MinValue, int.MaxValue);
int highBits = r.Number(int.MinValue, int.MaxValue);

const int Scale = 28;

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
// 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;
}
}
}
32 changes: 1 addition & 31 deletions Source/Bogus/Randomizer.cs
Expand Up @@ -194,37 +194,7 @@ 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)
{
// Decimal: 128 bits wide
// bit 0: sign bit
// bit 1-10: not used
// bit 11-15: scale (values 29, 30, 31 not used)
// bit 16-31: not used
// bit 32-127: mantissa (96 bits)

// Max value: 00000000 FFFFFFFF FFFFFFFF FFFFFFFF
// = 79228162514264337593543950335

// Max value with max scaling: 001C0000 FFFFFFFF FFFFFFFF FFFFFFFF
// = 7.9228162514264337593543950335

// Step 1: Generate a value with uniform distribution between 0 and this value.
// This ensures the greatest level of precision in the distribution of bits;
// the resulting value, after it is adjusted into the caller's desired range,
// should not skip any possible values at the least significant end of the
// mantissa.

int lowBits = Number(int.MinValue, int.MaxValue);
int middleBits = Number(int.MinValue, int.MaxValue);
int highBits = Number(int.MinValue, int.MaxValue);

const int Scale = 28;

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
// 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;
return Convert.ToDecimal(Double()) * (max - min) + min;
}

/// <summary>
Expand Down

0 comments on commit c5e697e

Please sign in to comment.