core: support signed ints and custom capacity in `Int::to_bytes`
authorAlexis Laferrière <alexis.laf@xymus.net>
Tue, 8 Aug 2017 16:23:18 +0000 (12:23 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Mon, 18 Sep 2017 19:28:29 +0000 (15:28 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/core/bytes.nit

index e375e60..6ef4a90 100644 (file)
@@ -651,7 +651,7 @@ private class BytesIterator
 end
 
 redef class Int
-       # A big-endian representation of self.
+       # A signed big-endian representation of `self`
        #
        # ~~~
        # assert     1.to_bytes.hexdigest ==     "01"
@@ -661,43 +661,86 @@ redef class Int
        # assert 65536.to_bytes.hexdigest == "010000"
        # ~~~
        #
+       # Negative values are converted to their two's complement.
+       # Be careful as the result can be ambiguous.
+       #
+       # ~~~
+       # assert     (-1).to_bytes.hexdigest ==     "FF"
+       # assert    (-32).to_bytes.hexdigest ==     "E0"
+       # assert   (-512).to_bytes.hexdigest ==   "FE00"
+       # assert (-65794).to_bytes.hexdigest == "FEFEFE"
+       # ~~~
+       #
+       # Optionally, set `n_bytes` to the desired number of bytes in the output.
+       # This setting can disambiguate the result between positive and negative
+       # integers. Be careful with this parameter as the result may overflow.
+       #
+       # ~~~
+       # assert        1.to_bytes(2).hexdigest ==     "0001"
+       # assert    65535.to_bytes(2).hexdigest ==     "FFFF"
+       # assert     (-1).to_bytes(2).hexdigest ==     "FFFF"
+       # assert   (-512).to_bytes(4).hexdigest == "FFFFFE00"
+       # assert 0x123456.to_bytes(2).hexdigest ==     "3456"
+       # ~~~
+       #
        # For 0, a Bytes object with single nul byte is returned (instead of an empty Bytes object).
        #
        # ~~~
        # assert 0.to_bytes.hexdigest == "00"
        # ~~~
        #
-       # `Bytes::to_i` can be used to do the reverse operation.
+       # For positive integers, `Bytes::to_i` can reverse the operation.
        #
        # ~~~
        # assert 1234.to_bytes.to_i == 1234
        # ~~~
        #
        # Require self >= 0
-       fun to_bytes: Bytes do
-               if self == 0 then return "\0".to_bytes
-               assert self > 0
+       fun to_bytes(n_bytes: nullable Int): Bytes do
+
+               # If 0, force using at least one byte
+               if self == 0 and n_bytes == null then n_bytes = 1
 
                # Compute the len (log256)
                var len = 1
                var max = 256
-               while self >= max do
+               var s = self.abs
+               while s >= max do
                        len += 1
                        max *= 256
                end
 
+               # Two's complement
+               s = self
+               if self < 0 then
+                       var ff = 0
+                       for j in [0..len[ do
+                               ff *= 0x100
+                               ff += 0xFF
+                       end
+
+                       s = ((-self) ^ ff) + 1
+               end
+
+               # Cut long values
+               if n_bytes != null and len > n_bytes then len = n_bytes
+
                # Allocate the buffer
-               var res = new Bytes.with_capacity(len)
-               for i in [0..len[ do res[i] = 0u8
+               var cap = n_bytes or else len
+               var res = new Bytes.with_capacity(cap)
+
+               var filler = if self < 0 then 0xFFu8 else 0u8
+               for i in [0..cap[ do res[i] = filler
 
                # Fill it starting with the end
-               var i = len
-               var sum = self
-               while i > 0 do
+               var i = cap
+               var sum = s
+               while i > cap - len do
                        i -= 1
                        res[i] = (sum % 256).to_b
                        sum /= 256
                end
+
                return res
        end
 end