簡體   English   中英

包含范圍內的隨機浮點雙精度

[英]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,但我認為假設如下:

  • 0 和 1 應該等價地返回(即 Math.random() 不使用接近 0 的浮點數的更近間距)。
  • Math.random() >= 0.5 概率為 1/2(對於偶數基數應該為真)
  • 底層的 PRNG 足夠好,我們可以做到這一點。

請注意,隨機數總是成對生成的: while (a) 中的一個總是跟在if的一個或末尾 (b) 中的一個后面。 通過考慮返回 0 或 0.5 的 PRNG,很容易驗證它是否合理:

  • a=0 b=0 : 返回 0
  • a=0 b=0.5 : 返回 0.5
  • a=0.5 b=0 : 返回 1
  • a=0.5 b=0.5 : 循環

問題:

  • 假設可能不正確。 特別是,常見的 PRNG 是取 48 位 LCG 的前 32 位(Firefox 和 Java 這樣做)。 要生成雙精度數,您需要從兩個連續輸出中取出 53 位並除以 2 53 ,但有些輸出是不可能的(您不能生成 2 53具有 48 位狀態的輸出!)。 我懷疑其中一些永遠不會返回 0(假設是單線程訪問),但我現在不想檢查 Java 的實現。
  • 由於需要獲得額外的位,Math.random() 對於每個潛在輸出是兩次,但這對 PRNG 施加了更多限制(需要我們推理上述 LCG 的四個連續輸出)。
  • Math.random() 每個輸出平均被調用四次 有點慢。
  • 它確定性地丟棄結果(假設單線程訪問),因此幾乎可以保證減少輸出空間。

我對這個問題的解決方案一直是使用以下內容代替您的上限。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM