浮点RNG是否应该更精确地接近0?

时间:2018-08-21 06:23:37

标签: haskell random floating-point precision

System.Random中的浮点RNG看起来很简单,但对我来说却不准确:

instance Random Double where
  randomR = randomRFloating
  random rng     = 
    case random rng of 
      (x,rng') -> 
          -- We use 53 bits of randomness corresponding to the 53 bit significand:
          ((fromIntegral (mask53 .&. (x::Int64)) :: Double)  
       /  fromIntegral twoto53, rng')
   where 
    twoto53 = (2::Int64) ^ (53::Int64)
    mask53 = twoto53 - 1

尽管此RNG确实可以统一生成FP编号,但我对此有疑问: RNG无法生成的范围内有一些编号。

具体来说,“太”精确的数字。例如,此RNG可以生成(表示为二进制IEEE双精度FP;先后是符号,指数和尾数):

0 01111111101 0000000000000000000000000000000000000000000000000000

正好是¼,但不能产生:

0 01111111101 0000000000000000000000000000000000000000000000000001

因为最后一个1(很少)的精度太高。

我怀疑是否会发生这种情况,因此我编写了自己的统一FP RNG:

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Bifunctor
import System.Random

randomFloat1to2 :: (RandomGen g, Random a, RealFloat a) => g -> (a, g) -- Uniformly generates random Float among [1,2)
randomFloat1to2 g = first (1+) (random g)

randomFloatExp :: forall a g. (RandomGen g, Random a, RealFloat a) => Int -> g -> (a, g) -- Uniformly generates random Float among [0, 2^(exp+1))
randomFloatExp exp g = let
    (minexp, _) = floatRange (0 :: a)
    (upperHalf, g') = random g
    in if exp == minexp
        then (0, g') -- Denormal numbers treated as 0
        else if upperHalf
            then first (2^^exp *) (randomFloat1to2 g')
            else randomFloatExp (exp-1) g'

randomFloat :: (RandomGen g, Random a, RealFloat a) => g -> (a, g) -- Uniformly generates random Float among [0,1)
randomFloat = randomFloatExp (-1)

说明:

在[0,1)范围内的Double个数字中,[1 / 2,1)中的所有数字均具有IEEE指数01111111110,而其他数字则具有较低的数字。因此,RNG进行硬币翻转:

如果出现头部,则RNG通过将½和[1,2)之间的随机数相乘来在[1 / 2,1)中选择一个随机数。由于默认的random有效地选择了一个随机的尾数,因此我们可以向其添加1以使范围为[1,2)的RNG均匀。

如果不是,则RNG会通过[¼,½),[⅛,¼)等进行递归,直到范围不正常为止。

我的版本可以被认为是更好的版本吗?

1 个答案:

答案 0 :(得分:1)

  

浮点RNG是否应该更精确地设置为0?

取决于功能目标以及@Eric Postpischil的评论。

这就像问tan(x)是否比sin(x)好:这取决于目标。两者对于较小的x给出相同的答案,但在范围的另一部分有所不同。


  

我的版本可以被认为是更好的版本吗?

之所以不好的原因是:即使在最坏的情况下,完成工作的时间也可能非常长。

  

RNG通过[¼,½),[⅛,¼)等进行递归,直到范围不正常为止。

通过[¼,½),[⅛,¼)进行非正常递归可能需要进行数千次递归。

要帮助tad,代码可以生成一个随机指数,但仍然是递归的,但速度更快(除非随机数为0,否则n位random的MSBit变为指数,然后需要递归调用。)

这将递归减少了N倍(random位的位宽度)。

然而,解决方案不可能重复scores次,而需要使用不同的random来计算指数。