Skip to content

Commit 06af152

Browse files
committed
Fixed bugs with floating point equality checks in PERT distributions.
1 parent cc81a46 commit 06af152

14 files changed

Lines changed: 76 additions & 73 deletions

File tree

4.8.1/Numerics/Data/Statistics/Statistics.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ public static double[] ProductMoments(IList<double> data)
471471
{
472472
if (data == null) throw new ArgumentNullException(nameof(data));
473473
double N = data.Count;
474-
if (N < 4) throw new ArgumentException("There must be more than 4 data points.", nameof(data));
474+
if (N < 4) return [double.NaN, double.NaN, double.NaN, double.NaN];
475475

476476
// sums of powers
477477
double X1 = 0, X2 = 0, X3 = 0, X4 = 0;
@@ -524,8 +524,8 @@ public static double[] LinearMoments(IList<double> data)
524524
{
525525
if (data == null) throw new ArgumentNullException(nameof(data));
526526
double N = data.Count;
527-
if (N < 4) throw new ArgumentException("There must be more than 4 data points.", nameof(data));
528-
527+
if (N < 4) return [double.NaN, double.NaN, double.NaN, double.NaN];
528+
529529
// Copy and sort data
530530
var sortedData = data.ToArray();
531531
Array.Sort(sortedData);

4.8.1/Numerics/Distributions/Univariate/GeneralizedBeta.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static GeneralizedBeta PERT(double min, double mode, double max, double s
124124
// get alpha and beta
125125
double mean = (min + scale * mode + max) / (scale + 2d);
126126
double alpha = 1d + scale / 2d;
127-
if (mean != mode)
127+
if (!mean.AlmostEquals(mode))
128128
{
129129
alpha = (mean - min) * (2d * mode - min - max) / ((mode - mean) * (max - min));
130130
}

4.8.1/Numerics/Distributions/Univariate/Pert.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public override double Mean
186186
{
187187
get
188188
{
189-
if (_min == _max && _min == _mode) return _min;
189+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return _min;
190190
return (Min + 4 * MostLikely + Max) / 6;
191191
}
192192
}
@@ -196,7 +196,7 @@ public override double Median
196196
{
197197
get
198198
{
199-
if (_min == _max && _min == _mode) return _min;
199+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return _min;
200200
return (Min + 6 * MostLikely + Max) / 8;
201201
}
202202
}
@@ -405,7 +405,7 @@ public override double PDF(double x)
405405
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
406406
// These checks are done specifically for an application where a
407407
// user inputs min = max = mode
408-
if (_min == _max && _min == _mode) return 0.0d;
408+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return 0.0d;
409409
if (double.IsNaN(_mode)) return 0.0d;
410410
return _beta.PDF(x);
411411
}
@@ -417,7 +417,7 @@ public override double CDF(double x)
417417
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
418418
// These checks are done specifically for an application where a
419419
// user inputs min = max = mode
420-
if (_min == _max && _min == _mode) return 1d;
420+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return 1d;
421421
if (double.IsNaN(_mode)) return 1;
422422
return _beta.CDF(x);
423423
}
@@ -429,7 +429,7 @@ public override double InverseCDF(double probability)
429429
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
430430
// These checks are done specifically for an application where a
431431
// user inputs min = max = mode
432-
if (_min == _max && _min == _mode) return Min;
432+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return Min;
433433
if (double.IsNaN(_mode)) return Min;
434434
return _beta.InverseCDF(probability);
435435
}

4.8.1/Numerics/Distributions/Univariate/PertPercentile.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public override double Mean
220220
{
221221
get
222222
{
223-
if (_beta.Min == _beta.Max) return _beta.Min;
223+
if (_beta.Min.AlmostEquals(_beta.Max)) return _beta.Min;
224224
return _beta.Mean < MinAllowableValue ? MinAllowableValue : _beta.Mean > MaxAllowableValue ? MaxAllowableValue : _beta.Mean;
225225
}
226226
}
@@ -230,7 +230,7 @@ public override double Median
230230
{
231231
get
232232
{
233-
if (_beta.Min == _beta.Max) return _beta.Min;
233+
if (_beta.Min.AlmostEquals(_beta.Max)) return _beta.Min;
234234
return _beta.Median < MinAllowableValue ? MinAllowableValue : _beta.Median > MaxAllowableValue ? MaxAllowableValue : _beta.Median;
235235
}
236236
}
@@ -240,7 +240,7 @@ public override double Mode
240240
{
241241
get
242242
{
243-
if (_beta.Min == _beta.Max) return _beta.Min;
243+
if (_beta.Min.AlmostEquals(_beta.Max)) return _beta.Min;
244244
return _beta.Mode < MinAllowableValue ? MinAllowableValue : _beta.Mode > MaxAllowableValue ? MaxAllowableValue : _beta.Mode;
245245
}
246246
}
@@ -404,7 +404,7 @@ public override double PDF(double x)
404404
if (_parametersSolved == false) SolveParameters();
405405
// These checks are done specifically for an application where a
406406
// user inputs min = max = mode
407-
if (_beta.Min == _beta.Max) return 0.0d;
407+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return 0.0d;
408408
if (double.IsNaN(_beta.Mode)) return 0.0d;
409409
//
410410
if (x < MinAllowableValue) x = MinAllowableValue;
@@ -419,7 +419,7 @@ public override double CDF(double x)
419419
if (_parametersSolved == false) SolveParameters();
420420
// These checks are done specifically for an application where a
421421
// user inputs min = max = mode
422-
if (_beta.Min == _beta.Max) return 1d;
422+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return 1d;
423423
if (double.IsNaN(_beta.Mode)) return 1d;
424424
//
425425
if (x < MinAllowableValue) x = MinAllowableValue;
@@ -439,7 +439,7 @@ public override double InverseCDF(double probability)
439439
if (x > MaxAllowableValue) x = MaxAllowableValue;
440440
// These checks are done specifically for an application where a
441441
// user inputs min = max = mode
442-
if (_beta.Min == _beta.Max) return x;
442+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return x;
443443
if (double.IsNaN(_beta.Mode)) return x;
444444
//
445445
try

4.8.1/Numerics/Distributions/Univariate/PertPercentileZ.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public override double Mean
209209
{
210210
get
211211
{
212-
if (_beta.Min == _beta.Max) return Normal.StandardCDF(_beta.Min);
212+
if (_beta.Min.AlmostEquals(_beta.Max)) return Normal.StandardCDF(_beta.Min);
213213
return Normal.StandardCDF(_beta.Mean);
214214
}
215215
}
@@ -219,7 +219,7 @@ public override double Median
219219
{
220220
get
221221
{
222-
if (_beta.Min == _beta.Max) return Normal.StandardCDF(_beta.Min);
222+
if (_beta.Min.AlmostEquals(_beta.Max)) return Normal.StandardCDF(_beta.Min);
223223
return Normal.StandardCDF(_beta.Median);
224224
}
225225
}
@@ -229,7 +229,7 @@ public override double Mode
229229
{
230230
get
231231
{
232-
if (_beta.Min == _beta.Max) return Normal.StandardCDF(_beta.Min);
232+
if (_beta.Min.AlmostEquals(_beta.Max)) return Normal.StandardCDF(_beta.Min);
233233
return Normal.StandardCDF(_beta.Mode);
234234
}
235235
}
@@ -413,7 +413,7 @@ public override double PDF(double x)
413413
if (_parametersSolved == false) SolveParameters();
414414
// These checks are done specifically for an application where a
415415
// user inputs min = max = mode
416-
if (_beta.Min == _beta.Max) return 0.0d;
416+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return 0.0d;
417417
if (double.IsNaN(_beta.Mode)) return 0.0d;
418418
//
419419
return _beta.PDF(Normal.StandardZ(x));
@@ -428,7 +428,7 @@ public override double CDF(double x)
428428
if (_parametersSolved == false) SolveParameters();
429429
// These checks are done specifically for an application where a
430430
// user inputs min = max = mode
431-
if (_beta.Min == _beta.Max) return 1d;
431+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return 1d;
432432
if (double.IsNaN(_beta.Mode)) return 1d;
433433
//
434434
return _beta.CDF(Normal.StandardZ(x));
@@ -443,7 +443,7 @@ public override double InverseCDF(double probability)
443443
var x = Normal.StandardCDF(_beta.Min);
444444
// These checks are done specifically for an application where a
445445
// user inputs min = max = mode
446-
if (_beta.Min == _beta.Max) return x;
446+
if (_beta.Min.AlmostEquals(_beta.Max) && _beta.Min.AlmostEquals(_beta.Mode)) return x;
447447
if (double.IsNaN(_beta.Mode)) return x;
448448
//
449449
try

4.8.1/Numerics/Distributions/Univariate/Triangular.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ public override double PDF(double x)
399399
if (_parametersValid == false)
400400
ValidateParameters(Min, MostLikely, Max, true);
401401
//
402-
if (Min == Max && Min == MostLikely) return 0.0d;
402+
if (Min.AlmostEquals(Max) && Min.AlmostEquals(MostLikely)) return 0.0d;
403403
if (x < Minimum || x > Maximum) return 0.0d;
404404
if (x >= Min && x < MostLikely)
405405
{
@@ -422,7 +422,7 @@ public override double CDF(double x)
422422
// Validate parameters
423423
if (_parametersValid == false)
424424
ValidateParameters(Min, MostLikely, Max, true);
425-
if (Min == Max && Min == MostLikely) return 1d;
425+
if (Min.AlmostEquals(Max) && Min.AlmostEquals(MostLikely)) return 1d;
426426
if (x <= Minimum) return 0d;
427427
if (x >= Maximum) return 1d;
428428
if (x > Min && x <= MostLikely)
@@ -442,7 +442,7 @@ public override double InverseCDF(double probability)
442442
// Validate probability
443443
if (probability < 0.0d || probability > 1.0d)
444444
throw new ArgumentOutOfRangeException("probability", "Probability must be between 0 and 1.");
445-
if (Min == Max && Min == MostLikely) return Min;
445+
if (Min.AlmostEquals(Max) && Min.AlmostEquals(MostLikely)) return Min;
446446
if (probability == 0.0d) return Minimum;
447447
if (probability == 1.0d) return Maximum;
448448
// Validate parameters

4.8.1/Numerics/Utilities/ExtensionMethods.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public static T GetAttributeOfType<T>(this Enum enumValue) where T : Attribute
7878
/// <param name="epsilon">The absolute tolerance level. Default = 1E-15.</param>
7979
public static bool AlmostEquals(this double a, double b, double epsilon = 1E-15)
8080
{
81-
return Math.Abs(a - b) < epsilon;
81+
return Math.Abs(a - b) <= epsilon;
8282
}
8383

8484
#endregion

8.0/Numerics/Data/Statistics/Statistics.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ public static double[] ProductMoments(IList<double> data)
466466
{
467467
if (data == null) throw new ArgumentNullException(nameof(data));
468468
double N = data.Count;
469-
if (N < 4) throw new ArgumentException("There must be more than 4 data points.", nameof(data));
469+
if (N < 4) return [double.NaN, double.NaN, double.NaN, double.NaN];
470470

471471
// sums of powers
472472
double X1 = 0, X2 = 0, X3 = 0, X4 = 0;
@@ -519,7 +519,7 @@ public static double[] LinearMoments(IList<double> data)
519519
{
520520
if (data == null) throw new ArgumentNullException(nameof(data));
521521
double N = data.Count;
522-
if (N < 4) throw new ArgumentException("There must be more than 4 data points.", nameof(data));
522+
if (N < 4) return [double.NaN, double.NaN, double.NaN, double.NaN];
523523

524524
// Copy and sort data
525525
var sortedData = data.ToArray();

8.0/Numerics/Distributions/Univariate/GeneralizedBeta.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public static GeneralizedBeta PERT(double min, double mode, double max, double s
124124
// get alpha and beta
125125
double mean = (min + scale * mode + max) / (scale + 2d);
126126
double alpha = 1d + scale / 2d;
127-
if (mean != mode)
127+
if (!mean.AlmostEquals(mode))
128128
{
129129
alpha = (mean - min) * (2d * mode - min - max) / ((mode - mean) * (max - min));
130130
}

8.0/Numerics/Distributions/Univariate/Pert.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,20 +184,20 @@ public override double[] GetParameters
184184
/// <inheritdoc/>
185185
public override double Mean
186186
{
187-
get
187+
get
188188
{
189-
if (_min == _max && _min == _mode) return _min;
190-
return (Min + 4 * MostLikely + Max) / 6;
189+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return _min;
190+
return (Min + 4 * MostLikely + Max) / 6;
191191
}
192192
}
193193

194194
/// <inheritdoc/>
195195
public override double Median
196196
{
197-
get
197+
get
198198
{
199-
if (_min == _max && _min == _mode) return _min;
200-
return (Min + 6 * MostLikely + Max) / 8;
199+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return _min;
200+
return (Min + 6 * MostLikely + Max) / 8;
201201
}
202202
}
203203

@@ -405,7 +405,7 @@ public override double PDF(double x)
405405
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
406406
// These checks are done specifically for an application where a
407407
// user inputs min = max = mode
408-
if (_min == _max && _min == _mode) return 0.0d;
408+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return 0.0d;
409409
if (double.IsNaN(_mode)) return 0.0d;
410410
return _beta.PDF(x);
411411
}
@@ -417,7 +417,7 @@ public override double CDF(double x)
417417
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
418418
// These checks are done specifically for an application where a
419419
// user inputs min = max = mode
420-
if (_min == _max && _min == _mode) return 1d;
420+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return 1d;
421421
if (double.IsNaN(_mode)) return 1;
422422
return _beta.CDF(x);
423423
}
@@ -429,7 +429,7 @@ public override double InverseCDF(double probability)
429429
if (_parametersValid == false) ValidateParameters(Min, Mode, Max, true);
430430
// These checks are done specifically for an application where a
431431
// user inputs min = max = mode
432-
if (_min == _max && _min == _mode) return Min;
432+
if (_min.AlmostEquals(_max) && _min.AlmostEquals(_mode)) return Min;
433433
if (double.IsNaN(_mode)) return Min;
434434
return _beta.InverseCDF(probability);
435435
}

0 commit comments

Comments
 (0)