Skip to content

Commit a8739f7

Browse files
fix(normal-distribution): prevent ±infinity samples from Box-Muller
Sample u1 and u2 from the open interval (0, 1) to avoid log(0) = -∞. The batch method now consumes both outputs of each Box-Muller pair, halving the number of transcendental function calls. Fixes: #178
1 parent ef6f9d0 commit a8739f7

1 file changed

Lines changed: 18 additions & 8 deletions

File tree

Sources/StatKit/Descriptive Statistics/Distribution/Continuous/NormalDistribution.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,29 @@ public struct NormalDistribution: ContinuousDistribution, UnivariateDistribution
6060
}
6161

6262
public func sample() -> Double {
63-
let u1 = Double.random(in: 0 ... 1)
64-
let u2 = Double.random(in: 0 ... 1)
63+
let u1 = Double.random(in: Double.leastNonzeroMagnitude ..< 1)
64+
let u2 = Double.random(in: Double.leastNonzeroMagnitude ..< 1)
6565
return mean + (-2 * variance * .log(u1)).squareRoot() * .sin(2 * .pi * u2)
6666
}
67-
67+
6868
public func sample(_ numberOfElements: Int) -> [Double] {
6969
precondition(0 < numberOfElements, "The requested number of samples need to be greater than 0.")
70-
70+
7171
var uniformGenerator = Xoroshiro256StarStar()
72-
return (1 ... numberOfElements).map { _ in
73-
let u1 = Double.random(in: 0 ... 1, using: &uniformGenerator)
74-
let u2 = Double.random(in: 0 ... 1, using: &uniformGenerator)
75-
return mean + (-2 * variance * .log(u1)).squareRoot() * .sin(2 * .pi * u2)
72+
var result = [Double]()
73+
result.reserveCapacity(numberOfElements)
74+
75+
while result.count < numberOfElements {
76+
let u1 = Double.random(in: Double.leastNonzeroMagnitude ..< 1, using: &uniformGenerator)
77+
let u2 = Double.random(in: Double.leastNonzeroMagnitude ..< 1, using: &uniformGenerator)
78+
let radius = (-2 * variance * .log(u1)).squareRoot()
79+
let angle = 2 * Double.pi * u2
80+
result.append(mean + radius * .cos(angle))
81+
if result.count < numberOfElements {
82+
result.append(mean + radius * .sin(angle))
83+
}
7684
}
85+
86+
return result
7787
}
7888
}

0 commit comments

Comments
 (0)