【问题标题】:Hamming numbers and double precision汉明数和双精度
【发布时间】:2020-07-03 07:20:50
【问题描述】:

我正在玩在 Haskell 中生成 Hamming numbers,试图改进显而易见的(请原谅函数的命名)

mergeUniq :: Ord a => [a] -> [a] -> [a]
mergeUniq (x:xs) (y:ys) = case x `compare` y of
                               EQ -> x : mergeUniq xs ys
                               LT -> x : mergeUniq xs (y:ys)
                               GT -> y : mergeUniq (x:xs) ys

powers :: [Integer]
powers = 1 : expand 2 `mergeUniq` expand 3 `mergeUniq` expand 5
  where
    expand factor = (factor *) <$> powers

我注意到,如果我将数字表示为 2-、3- 和 5-指数的三元组,例如 data Power = Power { k2 :: !Int, k3 :: !Int, k5 :: !Int },我可以避免(较慢的)任意精度 Integer,其中数字被理解为 @ 987654329@。那么两个Powers的比较就变成了

instance Ord Power where
  p1 `compare` p2 = toComp (p1 `divP` gcdP) `compare` toComp (p2 `divP` gcdP)
    where
    divP p1 p2 = Power { k2 = k2 p1 - k2 p2, k3 = k3 p1 - k3 p2, k5 = k5 p1 - k5 p2 }
    gcdP = Power { k2 = min (k2 p1) (k2 p2), k3 = min (k3 p1) (k3 p2), k5 = min (k5 p1) (k5 p2) }
    toComp Power { .. } = fromIntegral k2 * log 2 + fromIntegral k3 * log 3 + fromIntegral k5 * log 5

所以,非常粗略地说,为了比较 p₁ = 2<sup>i₁</sup> * 3<sup>j₁</sup> * 5<sup>k₁</sup>p₂ = 2<sup>i₂</sup> * 3<sup>j₂</sup> * 5<sup>k₂</sup>,我们比较了 p₁p₂ 的对数,这大概适合 Double。但实际上我们做得更好:我们首先计算它们的 GCD(通过找到相应指数对的 mins - 到目前为止只有 Int 算术!),将 p₁p₂ 除以 GCD(通过减去mins 来自相应指数 - 也只有 Int 算术),并比较结果的对数。

但是,鉴于我们经过Doubles,最终会损失精度。这是我提出问题的基础:

  1. Doubles 的有限精度何时会咬我?也就是说,如何估计i, j, k 的阶数,2<sup>i</sup> * 3<sup>j</sup> * 5<sup>k</sup> 与具有“相似”指数的数字的比较结果将变得不可靠?
  2. 我们通过除以 GCD(这可能大大降低了此任务的指数)这一事实如何修改上一个问题的答案?

我做了一个实验,将这种方式产生的数字与通过任意精度算术产生的数字进行比较,并且所有汉明数精确到第 1'000'000'000 次匹配(这花了我大约 15 分钟和 600兆内存来验证)。但这显然不是证据。

【问题讨论】:

  • 你的问题 1 是 2^i•3^j•5^k 形式的最小数字 x 是多少,这样在该形式中还有另一个数字 y,并且 x Double 值会产生 X 和 Y,使得 Y ≤ X,因此通过比较 Double 中的对数无法区分 x 和 y?问题 2 是类似的,只是 2、3 或 5 的每个指数在 x 或 y 中至多有一个不为零?对数使用什么底? (基数的影响可能很小,但可能会产生舍入误差,这可能会影响第一次失败发生的位置。)
  • 十亿海明数的大小是多少?
  • 或者,更确切地说,我们没有直接在Double 中获得 x 和 y 的对数,但是我们使用 Double 算法从 2、3 和 5 的对数(每个乘以指数并将它们相加)?你有 2、3 和 5 的对数作为 Double 中最接近的可表示值(一些数学库可能有更大的错误,尽管对数比一些超越函数更容易计算)?
  • 答案是,如果记忆有用(但请检查the RosettaCode page),在万亿分之一的某个地方,甚至可能更高。您的 GCD 技巧很不错,但不幸的是,有一些三元组可以比较,它们没有共同的因素,所以最后我的猜测是没关系。我在 someanswer 的 SO 或 Rosetta 上的某个 IIRC 上提到了这个问题。
  • this answer 直接回答您的问题。它提到在计算第万亿个汉明数时使用了 14 个有效数字。

标签: algorithm haskell floating-point precision hamming-numbers


【解决方案1】:

Empirically,大约是十万亿分之一的汉明数,或者更高。

在这里使用你漂亮的 GCD 技巧对我们没有帮助,因为一些相邻的汉明数之间必然没有公因数。

更新: 在线尝试on ideone 和其他地方,我们得到

4T  5.81s 22.2MB  -- 16 digits used.... still good
                  --  (as evidenced by the `True` below), but really pushing it.
((True,44531.6794,7.275957614183426e-11),(16348,16503,873),"2.3509E+13405")
-- isTruly  max        min logval           nth-Hamming       approx.
--  Sorted   logval      difference          as i,j,k          value
--            in band      in band                             in decimal
10T   11.13s 26.4MB
((True,60439.6639,7.275957614183426e-11),(18187,23771,1971),"1.4182E+18194")
13T   14.44s 30.4MB    ...still good
((True,65963.6432,5.820766091346741e-11),(28648,21308,1526),"1.0845E+19857")

---- same code on tio:
10T   16.77s
35T   38.84s 
((True,91766.4800,5.820766091346741e-11),(13824,2133,32112),"2.9045E+27624")
70T   59.57s
((True,115619.1575,5.820766091346741e-11),(13125,13687,34799),"6.8310E+34804")

---- on home machine:
100T: 368.13s
((True,130216.1408,5.820766091346741e-11),(88324,876,17444),"9.2111E+39198")

140T: 466.69s
((True,145671.6480,5.820766091346741e-11),(9918,24002,42082),"3.4322E+43851")

170T: 383.26s         ---FAULTY---
((False,155411.2501,0.0),(77201,27980,14584),"2.80508E+46783")

【讨论】:

    【解决方案2】:

    我猜你可以使用自适应任意精度来计算日志。

    如果你选择 log base 2,那么log2(2^i) 是微不足道的。这消除了 1 个因素,而 log2 的优点是比自然对数更容易计算(https://en.wikipedia.org/wiki/Binary_logarithm 给出了一个算法,例如,还有 Shanks...)。

    对于 log2(3) 和 log2(5),您将开发出足够的术语来区分这两个操作数。我不知道它是否会导致比在大整数算术中直接对 3^j 和 5^k 取幂并计算高位更多的操作......但是这些可以预先列出到所需的位数。

    【讨论】:

      猜你喜欢
      • 2018-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多