From c2bd43956c28b683154d980fe9922e04a81bd123 Mon Sep 17 00:00:00 2001 From: bchavez Date: Sat, 15 Aug 2020 11:08:06 -0700 Subject: [PATCH] Reverts Random.Decimal() implementation from PR #300 to previous v29 implementation. #319 #320 --- HISTORY.md | 9 +++- Source/Bogus.Tests/GitHubIssues/Issue319.cs | 37 +++++++++++++++ Source/Bogus.Tests/RandomizerTest.cs | 2 +- .../Extensions/ExtensionsForRandomizer.cs | 45 +++++++++++++++++++ Source/Bogus/Randomizer.cs | 32 +------------ 5 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 Source/Bogus.Tests/GitHubIssues/Issue319.cs create mode 100644 Source/Bogus/Extensions/ExtensionsForRandomizer.cs diff --git a/HISTORY.md b/HISTORY.md index d82de5e6..f0c71924 100644 --- a/HISTORY.md +++ b/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 arithmetic `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. diff --git a/Source/Bogus.Tests/GitHubIssues/Issue319.cs b/Source/Bogus.Tests/GitHubIssues/Issue319.cs new file mode 100644 index 00000000..19adf40c --- /dev/null +++ b/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(); + } + } +} \ No newline at end of file diff --git a/Source/Bogus.Tests/RandomizerTest.cs b/Source/Bogus.Tests/RandomizerTest.cs index b9394ebc..868903f3 100644 --- a/Source/Bogus.Tests/RandomizerTest.cs +++ b/Source/Bogus.Tests/RandomizerTest.cs @@ -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] diff --git a/Source/Bogus/Extensions/ExtensionsForRandomizer.cs b/Source/Bogus/Extensions/ExtensionsForRandomizer.cs new file mode 100644 index 00000000..97f82e37 --- /dev/null +++ b/Source/Bogus/Extensions/ExtensionsForRandomizer.cs @@ -0,0 +1,45 @@ +namespace Bogus.Extensions +{ + public static class ExtensionsForRandomizer + { + /// + /// Get a random decimal, between 0.0 and 1.0. + /// + /// Minimum, default 0.0 + /// Maximum, default 1.0 + 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; + } + } +} \ No newline at end of file diff --git a/Source/Bogus/Randomizer.cs b/Source/Bogus/Randomizer.cs index dc85796e..e27251d8 100644 --- a/Source/Bogus/Randomizer.cs +++ b/Source/Bogus/Randomizer.cs @@ -194,37 +194,7 @@ public double Double(double min = 0.0d, double max = 1.0d) /// Maximum, default 1.0 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; } ///