我需要以与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
功能。
答案 0 :(得分:6)
只需从crypto-api窃取i2bs
和bs2i
例程并稍作修改即可:
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)