【问题标题】:Read / Write Haskell Integer in two's complement representation以二进制补码表示形式读取/写入 Haskell 整数
【发布时间】:2013-02-09 10:18:02
【问题描述】:

我需要以一种与 Java 对 BigInteger 类所做的兼容的方式读写整数:

返回一个包含二进制补码表示的字节数组 这个大整数。字节数组将采用大端字节序: 最高有效字节在第零个元素中。该数组将包含 表示此 BigInteger 所需的最小字节数, 包括至少一个符号位,即 (ceil((this.bitLength() + 1)/8))。

遗憾的是,这排除了Data.Binary 提供的内容。在图书馆的某个地方遵循这个约定,有没有什么有效的方法可以有效地进行ByteString Integer 转换?如果没有,怎么办?

根据 Thomas M. DuBuisson 的回答(以及以下讨论),我目前有

i2bs :: Integer -> B.ByteString
i2bs x
   | x == 0 = B.singleton 0
   | x < 0 = i2bs $ 2 ^ (8 * bytes) + x
   | otherwise = B.reverse $ B.unfoldr go x
   where
      bytes = (integerLogBase 2 (abs x) + 1) `quot` 8 + 1
      go i = if i == 0 then Nothing
                       else Just (fromIntegral i, i `shiftR` 8)

integerLogBase :: Integer -> Integer -> Int
integerLogBase b i =
     if i < b then
        0
     else
        -- Try squaring the base first to cut down the number of divisions.
        let l = 2 * integerLogBase (b*b) i
            doDiv :: Integer -> Int -> Int
            doDiv i l = if i < b then l else doDiv (i `div` b) (l+1)
        in  doDiv (i `div` (b^l)) l

这比我希望的更冗长,仍然错过了bs2i 函数。

【问题讨论】:

  • 它必须是可移植的,还是你可以假设带有integer-gmp 包的GHC?
  • 我更喜欢便携式解决方案。如果我没记错的话,即使是 GHC 也可以在没有 GMP 的情况下构建?那样就有点太脆弱了。
  • 是的,可以用integer-simple构建。如果您使用内部组件,您可能会更有效率。还有一个问题,你想要一个只包含数字位的ByteString,还是让它成为更大的ByteString 的一部分,以便你有一个字段来指定有多少字节组成表示?跨度>
  • 只需要原始位。

标签: haskell binary integer twos-complement


【解决方案1】:

只需从 crypto-api 中窃取 i2bsbs2i 例程并稍作修改即可:

import Data.ByteString as B

-- |@i2bs bitLen i@ converts @i@ to a 'ByteString'
i2bs :: Integer -> B.ByteString
i2bs = B.reverse . B.unfoldr (\i' -> if i' == 0 then Nothing
                                                else Just (fromIntegral i', i' `shiftR` 8))


-- |@bs2i bs@ converts the 'ByteString' @bs@ to an 'Integer' (inverse of 'i2bs')
bs2i :: B.ByteString -> Integer
bs2i = B.foldl' (\i b -> (i `shiftL` 8) + fromIntegral b) 0 . B.reverse

您可以通过首先确定位大小并使用原始i2bs 按顺序构造字节串(节省您的反向成本)来稍微提高效率。

(编辑:我应该注意这没有使用 Java 解析器进行测试,但是这个基本结构应该很容易让您改变以考虑任何丢失的位)。

【讨论】:

  • 这对于负数会失败,i2bs (-1) 不会返回(它会产生无限数量的0xff 字节)。
  • Arrgh,我只是(慢慢地)自己写一些东西。当然使用这个更方便。 @Waldheinz 只需计算您需要的字节数 (bytes = (integerLogBase (abs n) + 1) `quot` 8 + 1),然后将 2^(8*bytes) + n 写入 n &lt; 0
  • @DanielFischer 好吧,看来这个解决方案还不够,所以也许你的代码会是一个受欢迎的补充。
  • @Waldheinz 没错,这基本上适用于 NAT,需要您使用 abs 并添加自己的符号位。良好的观察力。
  • 呃,那会很丑,Data.ByteString.Internal 之类的。我不认为这是必要的。只需为 0 添加一个特殊情况(我想应该给出一个单字节 ByteString)并修复负数 n
【解决方案2】:

好的,基于 Thomas M. DuBuisson 的部分回答,一个完全可行的解决方案是:

bs2i :: B.ByteString -> Integer
bs2i b
   | sign = go b - 2 ^ (B.length b * 8)
   | otherwise = go b
   where
      go = B.foldl' (\i b -> (i `shiftL` 8) + fromIntegral b) 0
      sign = B.index b 0 > 127

i2bs :: Integer -> B.ByteString
i2bs x
   | x == 0 = B.singleton 0
   | x < 0 = i2bs $ 2 ^ (8 * bytes) + x
   | otherwise = B.reverse $ B.unfoldr go x
   where
      bytes = (integerLogBase 2 (abs x) + 1) `quot` 8 + 1
      go i = if i == 0 then Nothing
                       else Just (fromIntegral i, i `shiftR` 8)

integerLogBase :: Integer -> Integer -> Int
integerLogBase b i =
     if i < b then
        0
     else
        -- Try squaring the base first to cut down the number of divisions.
        let l = 2 * integerLogBase (b*b) i
            doDiv :: Integer -> Int -> Int
            doDiv i l = if i < b then l else doDiv (i `div` b) (l+1)
        in  doDiv (i `div` (b^l)) l

我不会很快接受我自己的答案,以防有人想提出更简洁的东西来展示他的技能。 :-)

【讨论】:

  • 对于 GHC,integerLogBase 是从 GHC.Float 导出的(它存在是因为将 fromRational 转换为 DoubleFloat 需要它)。这比 base 2 的效率要高得多,所以你最好使用它。但是,对于 32 位 GHC,|n| &gt;= 2^(2^31)(任一版本)会溢出,这可能适合内存 [对于 64 位 GHC,Integers 大到足以使其溢出,对于少数人来说,内存不适合更多年]。
  • @Niklas:我很高兴相信他,但对我来说,接受的问题答案应该符合问题的要求。这使得“使用”变得更容易:1)找到类似的问题。 2)复制+粘贴接受的答案。 3) 利润。但托马斯的回答与问题并不相符,这就是为什么我发布自己的答案以供参考和讨论。
  • @Waldheinz:虽然我不同意您应该复制并粘贴类似问题的答案(您应该将问题标记为重复!),但我同意将完全有效的答案作为已接受的答案对访问者。
  • @NiklasB。 : 我的意思不是使用 copypasta 来回答 SO 问题,而是在 SO 之外完成实际工作。 :-)
  • 不需要像我这样接受部分答案,如果它符合您的要求,请接受您自己的答案。
【解决方案3】:

这是一个无需先计算大小的解决方案。对于负数,它相当于反转所有位,执行计算,然后再次反转位。

i2bs :: Integer -> B.ByteString
i2bs x = B.reverse . B.unfoldr (fmap go) . Just $ changeSign x
  where
    changeSign :: Num a => a -> a
    changeSign | x < 0     = subtract 1 . negate
               | otherwise = id
    go :: Integer -> (Word8, Maybe Integer)
    go x = ( b, i )
      where
        b = changeSign (fromInteger x)
        i | x >= 128  = Just (x `shiftR` 8 )
          | otherwise = Nothing

bs2i :: B.ByteString -> Integer
bs2i xs = changeSign (B.foldl' go 0 xs)
  where
    changeSign :: Num a => a -> a
    changeSign | B.index xs 0 >= 128 = subtract 1 . negate
               | otherwise           = id
    go :: Integer -> Word8 -> Integer
    go i b = (i `shiftL` 8) + fromIntegral (changeSign b)

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 1970-01-01
  • 2021-12-21
相关资源
最近更新 更多