lib/msgpack/bigint.rb



# frozen_string_literal: true

module MessagePack
  module Bigint
    # We split the bigint in 32bits chunks so that individual part fits into
    # a MRI immediate Integer.
    CHUNK_BITLENGTH = 32
    FORMAT = 'CL>*'

    if Integer.instance_method(:[]).arity != 1 # Ruby 2.7 and newer
      # Starting from Ruby 2.7 we can address arbitrary bitranges inside an Integer with Integer#[]
      # This allows to not allocate any Integer.
      def self.to_msgpack_ext(bigint)
        members = []

        if bigint < 0
          bigint = -bigint
          members << 1
        else
          members << 0
        end

        offset = 0
        length = bigint.bit_length
        while offset < length
          members << bigint[offset, CHUNK_BITLENGTH]
          offset += CHUNK_BITLENGTH
        end

        members.pack(FORMAT)
      end
    else
      # On 2.6 and older since we can't address arbitrary bitranges, so we fallback to shifting the bigint.
      # This means that after each shift, we may allocate another Integer instance.
      BASE = (2**CHUNK_BITLENGTH) - 1
      def self.to_msgpack_ext(bigint)
        members = []

        if bigint < 0
          bigint = -bigint
          members << 1
        else
          members << 0
        end

        while bigint > 0
          members << (bigint & BASE)
          bigint = bigint >> CHUNK_BITLENGTH
        end

        members.pack(FORMAT)
      end
    end

    def self.from_msgpack_ext(data)
      parts = data.unpack(FORMAT)

      sign = parts.shift
      sum = parts.pop.to_i

      parts.reverse_each do |part|
        sum = sum << CHUNK_BITLENGTH
        sum += part
      end

      sign == 0 ? sum : -sum
    end
  end
end