【发布时间】: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