[英]Random floating point double in Inclusive Range
我們可以使用下面列出的函數輕松獲得所需范圍[X,Y)
內的隨機浮點數(注意 X 是包含的,Y 是不包含的),因為Math.random()
(以及大多數偽隨機數生成器,AFAIK)生成數字在[0,1)
:
function randomInRange(min, max) {
return Math.random() * (max-min) + min;
}
// Notice that we can get "min" exactly but never "max".
我們如何獲得包含兩個邊界的所需范圍內的隨機數,即[X,Y]
?
我想我們可以通過“滾動” IEE-754 浮點雙精度的位來“增加”我們從Math.random()
(或等價物)的值以將最大可能值精確地設置為 1.0 但這似乎很痛苦正確,尤其是在不適合位操作的語言中。 有更容易的方法嗎?
(順便說一句,為什么隨機數生成器在[0,1)
而不是[0,1]
生成數字?)
[編輯]請注意,我不需要這個,我完全知道這種區別是迂腐的。 只是好奇並希望得到一些有趣的答案。 如果這個問題不合適,請隨時投票結束。
我相信有更好的決定,但這個應該有效:)
function randomInRange(min, max) {
return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
首先,您的代碼存在問題:嘗試randomInRange(0,5e-324)
或在瀏覽器的 JavaScript 控制台中輸入Math.random()*5e-324
。
即使沒有上溢/下溢/規范,也很難可靠地推斷浮點運算。 經過一番挖掘,我可以找到一個反例:
>>> a=1.0
>>> b=2**-54
>>> rand=a-2*b
>>> a
1.0
>>> b
5.551115123125783e-17
>>> rand
0.9999999999999999
>>> (a-b)*rand+b
1.0
更容易解釋為什么 a=2 53和 b=0.5 會發生這種情況:2 53 -1 是下一個可表示的數字。 默認舍入模式(“舍入到最接近的偶數”)向上舍入 2 53 -0.5(因為 2 53是“偶數”[LSB = 0],而 2 53 -1 是“奇數”[LSB = 1]),因此您減去b
並得到 2 53 ,乘以得到 2 53 -1,再加上b
得到 2 53 。
回答你的第二個問題:因為底層 PRNG 幾乎總是在區間 [0,2 n -1] 中生成一個隨機數,即它生成隨機位。 選擇合適的 n(浮點表示中的精度位)並除以 2 n並獲得可預測的分布非常容易。 請注意, [0,1)
中的某些數字將永遠不會使用此方法生成( (0,2 -53 ) 中的任何數字與 IEEE 雙打)。
這也意味着你可以做a[Math.floor(Math.random()*a.length)]
而不必擔心溢出(作業:在 IEEE 二進制浮點數中,證明b < 1
意味着a*b < a
for正整數a
)。
另一個好處是您可以將每個隨機輸出 x 視為代表一個區間 [x,x+2 -53 ) (不太好的事情是返回的平均值略小於 0.5)。 如果你在 [0,1] 中返回,你是否以與其他所有事物相同的概率返回端點,或者它們應該只有一半的概率,因為它們只代表其他所有事物的一半區間?
為了回答在 [0,1] 中返回一個數字的簡單問題,下面的方法有效地生成了一個整數 [0,2 n ](通過在 [0,2 n+1 -1] 中生成一個整數並將其丟棄,如果它太大了)並除以 2 n :
function randominclusive() {
// Generate a random "top bit". Is it set?
while (Math.random() >= 0.5) {
// Generate the rest of the random bits. Are they zero?
// If so, then we've generated 2^n, and dividing by 2^n gives us 1.
if (Math.random() == 0) { return 1.0; }
// If not, generate a new random number.
}
// If the top bits are not set, just divide by 2^n.
return Math.random();
}
評論暗示基數為 2,但我認為假設如下:
請注意,隨機數總是成對生成的: while
(a) 中的一個總是跟在if
的一個或末尾 (b) 中的一個后面。 通過考慮返回 0 或 0.5 的 PRNG,很容易驗證它是否合理:
a=0 b=0
: 返回 0a=0 b=0.5
: 返回 0.5a=0.5 b=0
: 返回 1a=0.5 b=0.5
: 循環問題:
我對這個問題的解決方案一直是使用以下內容代替您的上限。
Math.nextAfter(upperBound,upperBound+1)
或者
upperBound + Double.MIN_VALUE
所以你的代碼看起來像這樣:
double myRandomNum = Math.random() * Math.nextAfter(upperBound,upperBound+1) + lowerBound;
或者
double myRandomNum = Math.random() * (upperBound + Double.MIN_VALUE) + lowerBound;
這只是通過最小的 double ( Double.MIN_VALUE
) 增加您的上限,以便您的上限將作為隨機計算的可能性包括在內。
這是一個很好的方法,因為它不會偏向任何一個數字的概率。
唯一不起作用的情況是您的上限等於Double.MAX_VALUE
只需選擇稍大的半開區間,這樣您選擇的閉區間就是一個子集。 然后,繼續生成隨機變量,直到它落在所述閉區間內。
例子:如果你想在 [3,8] 中得到均勻的東西,那么在 [3,9) 中反復重新生成一個均勻的隨機變量,直到它恰好落在 [3,8] 中。
function randomInRangeInclusive(min,max) {
var ret;
for (;;) {
ret = min + ( Math.random() * (max-min) * 1.1 );
if ( ret <= max ) { break; }
}
return ret;
}
注意:您生成半開 RV 的次數是隨機的,並且可能是無限的,但是您可以按照自己的喜好進行預期的調用次數,否則接近 1,我認為不存在不存在的解決方案t 可能無限次調用。
鑒於 0 和 1 之間的“非常大”數量的值,這真的很重要嗎? 實際命中 1 的機會很小,因此不太可能對您正在做的任何事情產生重大影響。
private static double random(double min, double max) {
final double r = Math.random();
return (r >= 0.5d ? 1.5d - r : r) * (max - min) + min;
}
我的經驗相當少,所以我也在尋找解決方案。
這是我粗略的想法:
隨機數生成器在 [0,1) 而不是 [0,1] 中生成數字,
因為 [0,1) 是一個單位長度,可以跟在 [1,2) 等后面而不重疊。
對於隨機 [x, y],您可以這樣做:
float randomInclusive(x, y){
float MIN = smallest_value_above_zero;
float result;
do{
result = random(x, (y + MIN));
} while(result > y);
return result;
}
[x, y] 中的所有值都有相同的可能性被選中,您現在可以到達 y。
Math.round()
將有助於包含綁定值。 如果您有0 <= value < 1
(1 是不包括的),那么Math.round(value * 100) / 100
返回0 <= value <= 1
(1 是包括在內)。 這里需要注意的是,該值現在的小數位只有 2 位數字。 如果你想要 3 位數,試試Math.round(value * 1000) / 1000
等等。 下面的函數還有一個參數,即小數位數 - 我稱之為精度:
function randomInRange(min, max, precision) {
return Math.round(Math.random() * Math.pow(10, precision)) /
Math.pow(10, precision) * (max - min) + min;
}
這個怎么樣?
function randomInRange(min, max){
var n = Math.random() * (max - min + 0.1) + min;
return n > max ? randomInRange(min, max) : n;
}
如果你堆棧溢出,我會給你買禮物。
- 編輯:別介意現在。 我很瘋狂:
randomInRange(0, 0.0000000000000000001)
並得到堆棧溢出。
在一個范圍內生成一個隨機浮點數是非常重要的。 例如,將隨機整數乘以或除以常數,或將統一浮點數縮放到所需范圍的常見做法的缺點是,並非浮點格式可以在范圍內表示的所有數字都可以以這種方式覆蓋,並且可能有微妙的偏見問題。 這些問題在 F. Goualard 的“通過除法整數生成隨機浮點數:案例研究”中詳細討論。
為了說明問題的重要性,以下偽代碼在閉區間 [lo, hi] 中生成一個隨機浮點數,其中數字的形式為 FPSign * FPSignificand * FPRADIX^FPExponent。 下面的偽代碼是從我關於浮點數生成的部分復制的。 請注意,它適用於浮點數的任何精度和任何基數(包括二進制和十進制)。
METHOD RNDRANGE(lo, hi)
losgn = FPSign(lo)
hisgn = FPSign(hi)
loexp = FPExponent(lo)
hiexp = FPExponent(hi)
losig = FPSignificand(lo)
hisig = FPSignificand(hi)
if lo > hi: return error
if losgn == 1 and hisgn == -1: return error
if losgn == -1 and hisgn == 1
// Straddles negative and positive ranges
// NOTE: Changes negative zero to positive
mabs = max(abs(lo),abs(hi))
while true
ret=RNDRANGE(0, mabs)
neg=RNDINT(1)
if neg==0: ret=-ret
if ret>=lo and ret<=hi: return ret
end
end
if lo == hi: return lo
if losgn == -1
// Negative range
return -RNDRANGE(abs(lo), abs(hi))
end
// Positive range
expdiff=hiexp-loexp
if loexp==hiexp
// Exponents are the same
// NOTE: Automatically handles
// subnormals
s=RNDINTRANGE(losig, hisig)
return s*1.0*pow(FPRADIX, loexp)
end
while true
ex=hiexp
while ex>MINEXP
v=RNDINTEXC(FPRADIX)
if v==0: ex=ex-1
else: break
end
s=0
if ex==MINEXP
// Has FPPRECISION or fewer digits
// and so can be normal or subnormal
s=RNDINTEXC(pow(FPRADIX,FPPRECISION))
else if FPRADIX != 2
// Has FPPRECISION digits
s=RNDINTEXCRANGE(
pow(FPRADIX,FPPRECISION-1),
pow(FPRADIX,FPPRECISION))
else
// Has FPPRECISION digits (bits), the highest
// of which is always 1 because it's the
// only nonzero bit
sm=pow(FPRADIX,FPPRECISION-1)
s=RNDINTEXC(sm)+sm
end
ret=s*1.0*pow(FPRADIX, ex)
if ret>=lo and ret<=hi: return ret
end
END METHOD
這個問題類似於問, 1.0 之前的浮點數是多少? 有這樣一個浮點數,但它是 2^24 中的一個(對於 IEEE float
)或 2^53 中的一個(對於double
)。
在實踐中,差異可以忽略不計。
在什么情況下您需要一個包含上限的浮點值? 對於我理解的整數,但對於浮點數,包含和排除之間的區別就像 1.0e-32。
這么想吧。 如果您想象浮點數具有任意精度,那么精確獲得min
的機會為零。 獲得max
的機會也是如此。 我會讓你得出你自己的結論。
這個'問題'相當於在0到1之間的實線上隨機得到一個點。沒有'包含'和'不包含'。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.