【问题标题】:Should Floating-Point RNG be preciser near 0?浮点 RNG 是否应该在 0 附近更精确?
【发布时间】:2019-01-27 06:59:11
【问题描述】:

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) 中的所有数字都具有 IEEE 指数 01111111110,而其他数字具有较小的指数。所以 RNG 掷硬币:

如果出现正面,RNG 通过将 ½ 与 [1,2) 中的随机数相乘,在 [½,1) 中选择一个随机数。由于默认的random 有效地选择了一个随机尾数,我们可以将其加 1 以生成范围 [1,2) 的统一 RNG。

如果不是,则 RNG 通过 [¼,½)、[⅛,¼) 等进行递归,直到范围非正规。

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

【问题讨论】:

  • 软件的质量是它服务于要实现的目标的程度。在这种情况下要达到的目标是什么?如果生成器的客户端只想要一些均匀分布的样本,那么第一个生成器就很好。如果您想尽可能精细地对实数上的均匀分布进行建模,那么这可能会更好。但是,如果我们从实数上的均匀分布中选择一个样本并将其四舍五入到最接近的可表示值,我们将不会得到您的分布,因为位于 binade 低端的点(½,¼,...)应该不那么频繁……
  • ... 比 binade 内的点,因为对于 binade 内的点, (x−½u, x+½u) 中的所有点都舍入为可表示的值 x,其中 u 是 binande 的 ULP (并且端点可能包括也可能不包括,取决于 x 的低位),但是,对于 x 低端点,只有在 (x−¼u, x+½u) 中指向 x,因为低于 x-¼u ,下一个较低的binade中的高值更接近。另一个考虑因素是客户将如何使用样本。一个常见的做法是乘以某个 b 并添加 a,因此缩放到一个区间 [a, a+b)....
  • ... 即使 a 和 b 只有 1,您的微调也会消失,因为添加 1 时会丢失低位。并且在乘法和加法过程中舍入的效果(使用 a 和 b 的其他值,而不仅仅是 1)可能会扭曲分布。所以,再一次,什么生成器适合什么应用程序取决于应用程序。
  • @EricPostpischil “如果你想尽可能精细地模拟实数上的均匀分布”,是的,这就是我的目标。但由于 RNG 应该用于右开范围,而不是“将其四舍五入到最接近的可表示值”,因此它是“向下四舍五入”。

标签: haskell random floating-point precision


【解决方案1】:

浮点RNG应该在0附近更精确吗?

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

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


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

它不是更好的一个原因:在最坏的情况下完成时间可能会非常长 - 即使它可能很少见。

RNG 通过 [¼,½)、[⅛,¼) 等进行递归,直到范围非正规。

通过 [¼,½), [⅛,¼) 递归到非正规可能需要 数千次递归

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

这将递归减少了 N 的一个因子(位宽为random 数字)。

然而,一个不会递归 scores 次的解决方案需要不同的 random 来计算指数。

【讨论】:

  • 不过,由于我的原始版本具有 O(2ⁿ) 时间复杂度(其中 n 是 FP 类型的指数位的长度)并且您将它除以一个常数因子,它仍然是 @987654331 @。另外,如果我为 FP RNG 生成一个随机的单个 2ⁿ 位整数,时间复杂度会更差Θ(2ⁿ),因为系统 RNG 只能生成具有固定位长的整数。
猜你喜欢
  • 2011-12-17
  • 2012-11-06
  • 2021-01-03
  • 2010-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-23
  • 2023-03-23
相关资源
最近更新 更多