以二进制补码表示读/写Haskell Integer

时间:2013-02-24 00:05:01

标签: haskell binary integer twos-complement

我需要以与Java对BigInteger类所做的兼容的方式读写Integers:

  

返回包含二进制补码表示形式的字节数组   这个BigInteger。字节数组将采用big-endian字节顺序:   最重要的字节在第0个元素中。该数组将包含   表示此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功能。

3 个答案:

答案 0 :(得分:6)

只需从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解析器测试的,但是这个基本的构造应该很容易变异以考虑任何丢失的位)。

答案 1 :(得分:1)

好的,基于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

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

答案 2 :(得分:0)

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

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)