Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read / Write Haskell Integer in two's complement representation

I need to read and write Integers in a way that is compatible with what Java does with it's BigInteger class:

Returns a byte array containing the two's-complement representation of this BigInteger. The byte array will be in big-endian byte-order: the most significant byte is in the zeroth element. The array will contain the minimum number of bytes required to represent this BigInteger, including at least one sign bit, which is (ceil((this.bitLength() + 1)/8)).

Sadly, this rules out what Data.Binary offers. Is there something efficient to do a ByteString <-> Integer conversion following this convention somewhere in the libraries? If not, how can it be done?

Based on the answer from Thomas M. DuBuisson (and the following discussion) I currently have

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

Which is more verbose than what I was hoping for, still misses the bs2i function.

like image 827
Waldheinz Avatar asked Feb 24 '13 00:02

Waldheinz


2 Answers

Just steal the i2bs and bs2i routines from crypto-api and give them a slight modification:

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

You can make this slightly more efficient by determining the bit size first and use the original i2bs to construct the bytestring in order (saving you the cost of reverse).

(EDIT: I should note this isn't tested with a Java parser, but this basic construct should be easy for you to mutate to take into account any missing bits).

like image 92
Thomas M. DuBuisson Avatar answered Nov 20 '22 20:11

Thomas M. DuBuisson


Ok, so a fully working solution based on the partial answer by Thomas M. DuBuisson is:

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

I won't accept my own answer anytime soon in case someone wants to come up with something more neat to show of his skills. :-)

like image 1
Waldheinz Avatar answered Nov 20 '22 20:11

Waldheinz