Class: BitInt::Base Abstract

Inherits:
Numeric
  • Object
show all
Defined in:
lib/bitint/base.rb

Overview

This class is abstract.

Subclasses must be created via ‘Base.create`

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(integer, wrap: true) ⇒ Base

Creates a new instance with the integer.

Example

puts BitInt::U8.new(27) #=> 27
puts BitInt::U8.new(-1) #=> 255
puts BitInt::U8.new(-1, wrap: false) #=> OverflowError
puts BitInt::I8.new(255) #=> -1

Parameters:

  • integer (Integer)

    the integer to use

  • wrap (bool) (defaults to: true)

    changes how out-of-bounds integers are handled. When true, they’re wrapped; when false, an OverflowError to be raised.

Raises:

  • (OverflowError)

    raised when wrap is false and integer is out of bounds.



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/bitint/base.rb', line 174

def initialize(integer, wrap: true)
  unless wrap || self.class.in_bounds?(integer)
    exc = OverflowError.new(integer, self.class::BOUNDS)
    exc.set_backtrace(
      caller_locations(1) #: Array[Thread::Backtrace::Location]
    )
    raise exc
  end

  @int = self.class.wrap(integer)
  @wrap = wrap
end

Class Method Details

.create(bits: nil, bytes: nil, signed:) ⇒ singleton(Base)

Creates a new BitInt::Base subclass.

Example

puts BitInt::Base.create(bits: 8, signed: true).new(128)   #=> -127
puts BitInt::Base.create(bytes: 1, signed: false).new(256) #=> 0

Parameters:

  • bits (Integer?) (defaults to: nil)

    the amount of bits the subclass should have. Must be at least 1.

  • bytes (Integer?) (defaults to: nil)

    convenience argument; if supplied, sets bits to bytes * 8. Cannot be used with bits

  • signed (bool)

    Whether the subclass is a signed.

Returns:

  • (singleton(Base))

    A subclass of BitInt::Base; subclasses are cached, so repeated calls return the same subclass.

Raises:

  • (ArgumentError)

    When bits is negative or zero

  • (ArgumentError)

    If both bits and bytes are supplied



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/bitint/base.rb', line 47

def create(bits: nil, bytes: nil, signed:)
  if bits.nil? == bytes.nil?
    raise ArgumentError, "exactly one of 'bits' or 'bytes' must be supplied", caller(1)
  end

  bits ||= (_ = bytes) * 8

  unless bits.positive?
    raise ArgumentError, 'bit count must be positive', caller(1)
  end

  key = [bits, signed].freeze #: [Integer, bool]
  @classes[key] ||= Class.new(Base) do |cls|
    (_ = cls).setup!(bits, signed)
  end
end

.in_bounds?(integer) ⇒ bool

This method is abstract.

Only defined on subclasses

Check to see if integer is in bounds for self

Example

puts BitInt::I16.in_bounds?(32767) #=> true
puts BitInt::I16.in_bounds?(32768) #=> false
puts BitInt::U32.in_bounds?(-1)     #=> false

Parameters:

  • integer (Integer)

    the integer to check

Returns:

  • (bool)

    whether the integer is in bounds



154
155
156
157
# File 'lib/bitint/base.rb', line 154

def in_bounds?(integer)
  # TODO: use `self::BOUNDS`
  (self::MIN.to_i .. self::MAX.to_i).include? integer
end

.inspectObject Also known as: to_s

Returns a debugging string representation of this class

If the class is one of the builtins (eg BitInt::U16), it uses its name as the string, otherwise it uses a debugging representation

Example

p BitInt::U16   #=> BitInt::U16
p BitInt::U(17) #=> #<BitInt::Base @bits=17 @signed=false>


121
# File 'lib/bitint/base.rb', line 121

def inspect = name || "#<BitInt::Base @bits=#@bits @signed=#@signed>"

.signed?Boolean

This method is abstract.

Only defined on subclasses

Returns whether this class represents a signed integer.

Example

puts BitInt::U8.signed? #=> false
puts BitInt::I8.signed? #=> true

Returns:

  • (Boolean)


98
# File 'lib/bitint/base.rb', line 98

def signed? = @signed

.unsigned?Boolean

This method is abstract.

Only defined on subclasses

Returns whether this class represents an unsigned integer.

Example

puts BitInt::U8.unsigned? #=> true
puts BitInt::I8.unsigned? #=> false

Returns:

  • (Boolean)


109
# File 'lib/bitint/base.rb', line 109

def unsigned? = !signed?

.wrap(integer) ⇒ Integer

This method is abstract.

Only defined on subclasses

Wraps an integer to be within the bounds of self

Example

puts BitInt::I8.wrap(127) #=> 127
puts BitInt::I8.wrap(128) #=> -128
puts BitInt::I8.wrap(0xFF_FF_FF_FF_FF) #=> -1

Parameters:

  • integer (Integer)

    the integer to wrap

Returns:

  • (Integer)

    an integer guaranteed to be within self::BOUNDS.



137
138
139
# File 'lib/bitint/base.rb', line 137

def wrap(integer)
  ((integer - self::MIN.to_i) & self::MASK) + self::MIN.to_i
end

Instance Method Details

#%(rhs) ⇒ Object

Modulos self by rhs.



344
# File 'lib/bitint/base.rb', line 344

def %(rhs) = new_instance(@int % rhs.to_int)

#&(rhs) ⇒ Object

Bitwise ANDs self and rhs.



465
# File 'lib/bitint/base.rb', line 465

def &(rhs) = new_instance(@int & rhs.to_int)

#*(rhs) ⇒ Object

Multiplies self by rhs.



334
# File 'lib/bitint/base.rb', line 334

def *(rhs) = new_instance(@int * rhs.to_int)

#**(rhs) ⇒ Object

Raises self to the rhsth power.



349
# File 'lib/bitint/base.rb', line 349

def **(rhs) = new_instance((@int ** rhs.to_int).to_int) # TODO: use `Integer#pow(int, int)`

#+(rhs) ⇒ Object

Adds self to rhs.



324
# File 'lib/bitint/base.rb', line 324

def +(rhs) = new_instance(@int + rhs.to_int)

#-(rhs) ⇒ Object

Subtracts rhs from self.



329
# File 'lib/bitint/base.rb', line 329

def -(rhs) = new_instance(@int - rhs.to_int)

#-@Object

Numerically negates self.



313
# File 'lib/bitint/base.rb', line 313

def -@ = new_instance(-@int)

#/(rhs) ⇒ Object

Divides self by rhs.



339
# File 'lib/bitint/base.rb', line 339

def /(rhs) = new_instance(@int / rhs.to_int)

#<<(rhs) ⇒ Object

Shifts self left by rhs bits.



455
# File 'lib/bitint/base.rb', line 455

def <<(rhs) = new_instance(@int << rhs.to_int)

#<=>(rhs) ⇒ Object

Compares self to rhs.



319
# File 'lib/bitint/base.rb', line 319

def <=>(rhs) = defined?(rhs.to_i) ? @int <=> (_ = rhs).to_i : nil

#==(rhs) ⇒ Object

Checks to see if rhs is equal to selF

Example

U64 = BitInt::U(64)
twelve = U64.new(12)

# Behaves as you'd expect.
puts twelve == U64.new(12) #=> true
puts twelve == U64.new(13) #=> false
puts twelve == 12   #=> true
puts twelve == 12.0 #=> true
puts twelve == 13   #=> false
puts twelve == Object.new #=> false


281
282
283
# File 'lib/bitint/base.rb', line 281

def ==(rhs)
  defined?(rhs.to_i) && @int == rhs.to_i
end

#>>(rhs) ⇒ Object

Shifts self right by rhs bits.



460
# File 'lib/bitint/base.rb', line 460

def >>(rhs) = new_instance(@int >> rhs.to_int)

#[]Object

Gets the bit at index idx or returns nil.

This is equivalent to Integer#[]



482
# File 'lib/bitint/base.rb', line 482

def [](...) = __skip__ = @int.[](...)

#^(rhs) ⇒ Object

Bitwise XORs self and rhs.



475
# File 'lib/bitint/base.rb', line 475

def ^(rhs) = new_instance(@int ^ rhs.to_int)

#allbits?(mask) ⇒ Boolean

Returns true if all bits in ‘mask` are set in self.

Returns:

  • (Boolean)


492
# File 'lib/bitint/base.rb', line 492

def allbits?(mask) = @int.allbits?(mask)

#anybits?(mask) ⇒ Boolean

Returns true if any bit in ‘mask` is set in self.

Returns:

  • (Boolean)


487
# File 'lib/bitint/base.rb', line 487

def anybits?(mask) = @int.anybits?(mask)

#binObject

Returns a base-2 string of self. Equivalent to to_s(2).

Example

puts BitInt::U16.new(54321).bin #=> 0000010011010010


255
# File 'lib/bitint/base.rb', line 255

def bin = to_s(2)

#bit_lengthObject

Returns the amount of bits required to represent self

Unlike Integer#bit_length, this never changes and is equivalent to self.class::BITS.



505
# File 'lib/bitint/base.rb', line 505

def bit_length = self.class::BITS

#byte_lengthObject

Returns the amount of bytes required to represent self

This is equivalent to self.class::BYTES



512
# File 'lib/bitint/base.rb', line 512

def byte_length = self.class::BYTES

#bytes(endian = :native) ⇒ Object

Returns all the bytes that’re used represent self



517
518
519
# File 'lib/bitint/base.rb', line 517

def bytes(endian = :native)
  each_byte(endian).to_a
end

#coerce(other) ⇒ Object

Converts other to an instance of self, and returns a tuple of [<converted>, self]



260
# File 'lib/bitint/base.rb', line 260

def coerce(other) = [new_instance(other.to_int), self]

#downto(what) ⇒ Object

Same as Integer#downto, but returns instances of self.



401
402
403
404
405
406
407
408
409
# File 'lib/bitint/base.rb', line 401

def downto(what)
  return to_enum(_ = __method__) unless block_given?

  @int.downto what do |int|
    yield new_instance int
  end

  self
end

#each_byte(endian = :native) ⇒ Object

Executes the block once for each byte in self.

Bytes are converted to U8. If no block is given, returns an Enumerator



527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/bitint/base.rb', line 527

def each_byte(endian = :native)
  return to_enum(_ = __method__, endian) unless block_given?

  template = '_CS_L___Q'[self.class::BYTES]
  if template.nil? || template == '_'
    raise ArgumentError, 'bytes only works for sizes of 8, 16, 32, or 64.'
  end

  template.downcase! if self.class.signed?

  case endian
  when :native then # do nothing
  when :little then template.concat '<' unless self.class::BYTES == 1
  when :big    then template.concat '>' unless self.class::BYTES == 1
  else
    raise ArgumentError, 'endian must be :big, :little, or :native'
  end

  [@int].pack(template).unpack('C*').each do |byte| #: Integer
    yield U8.new(byte)
  end

  self
end

#eql?(rhs) ⇒ Boolean

Checks to see if rhs is another BitInt::Base of the same class, and have the same contents.

Returns:

  • (Boolean)


289
290
291
# File 'lib/bitint/base.rb', line 289

def eql?(rhs)
  rhs.is_a?(self.class) && @int == rhs.to_i
end

#even?Boolean

Checks to see if self is even.

Returns:

  • (Boolean)


376
# File 'lib/bitint/base.rb', line 376

def even? = @int.even?

#hashObject

Returns a hash code for this class.



296
# File 'lib/bitint/base.rb', line 296

def hash = [self.class, @int].hash

#hex(upper: false) ⇒ Object

Returns a base-16 string of self. Equivalent to to_s(16).

If upper: true is passed, returns an upper-case version.

Examples

puts BitInt::U16.new(1234).hex #=> 04d2
puts BitInt::U16.new(1234, upper: true).hex #=> 04D2


239
# File 'lib/bitint/base.rb', line 239

def hex(upper: false) = to_s(16).tap { _1.upcase! if upper }

#integer?Boolean

Always return true, as BitInt::Bases are always integers.

Returns:

  • (Boolean)


301
# File 'lib/bitint/base.rb', line 301

def integer? = true

#negative?Boolean

Return whether self is a negative integer. Zero is not negative.

Returns:

  • (Boolean)


361
# File 'lib/bitint/base.rb', line 361

def negative? = @int.negative?

#nobits?(mask) ⇒ Boolean

Returns true if no bits in ‘mask` are set in self.

Returns:

  • (Boolean)


497
# File 'lib/bitint/base.rb', line 497

def nobits?(mask) = @int.nobits?(mask)

#nonzero?Boolean

Returns a falsey value if zero, otherwise returns self.

Returns:

  • (Boolean)


371
# File 'lib/bitint/base.rb', line 371

def nonzero? = @int.nonzero? && self

#octObject

Returns a base-8 string of self. Equivalent to to_s(8).

Example

puts BitInt::U16.new(12345).oct #=> 30071


247
# File 'lib/bitint/base.rb', line 247

def oct = to_s(8)

#odd?Boolean

Checks to see if self is odd.

Returns:

  • (Boolean)


381
# File 'lib/bitint/base.rb', line 381

def odd? = @int.odd?

#positive?Boolean

Returns whether self is a positive integer. Zero is not positive.

Returns:

  • (Boolean)


356
# File 'lib/bitint/base.rb', line 356

def positive? = @int.positive?

#predObject

Gets the next value, or throws a OverFlowError if at the top.



436
437
438
439
# File 'lib/bitint/base.rb', line 436

def pred
  # TODO: this changes `@wrap`; that's weird
  new_instance(@int - 1, wrap: false)
end

#succObject

Gets the next value, or throws a OverFlowError if at the top.



428
429
430
431
# File 'lib/bitint/base.rb', line 428

def succ
  # TODO: this changes `@wrap`; that's weird
  new_instance(@int + 1, wrap: false)
end

#timesObject

Same as Integer#times, but returns instances of self.



387
388
389
390
391
392
393
394
395
# File 'lib/bitint/base.rb', line 387

def times
  return to_enum(_ = __method__) unless block_given?

  @int.times do |int|
    yield new_instance int
  end

  self
end

#to_fObject

Converts self to a Float.



202
# File 'lib/bitint/base.rb', line 202

def to_f = @int.to_f

#to_iObject Also known as: to_int

Returns the underlying integer.



196
# File 'lib/bitint/base.rb', line 196

def to_i = @int

#to_rObject

Converts self to a Rational.



207
# File 'lib/bitint/base.rb', line 207

def to_r = @int.to_r

#to_s(base = nil) ⇒ Object Also known as: inspect

Converts self to a String.

If no base is given, it just returns a normal String in base 10. If a base is given, a string padded with ‘0`s will be returned.

Examples

puts BitInt::U16.new(1234).to_s     #=> 1234
puts BitInt::U16.new(1234).to_s(16) #=> 04d2


221
222
223
224
225
226
227
# File 'lib/bitint/base.rb', line 221

def to_s(base = nil)
  return @int.to_s unless base
  base = base.to_int

  adjusted = negative? ? (-2*self.class::MIN.to_i + @int).to_i : @int
  adjusted.to_s(base).rjust(self.class::BITS / Math.log2(base), negative? ? '1' : '0')
end

#upto(what) ⇒ Object

Same as Integer#downto, but returns instances of self.



415
416
417
418
419
420
421
422
423
# File 'lib/bitint/base.rb', line 415

def upto(what)
  return to_enum(_ = __method__) unless block_given?

  @int.upto what do |int|
    yield new_instance int
  end

  self
end

#zero?Boolean

Returns whether self is zero.

Returns:

  • (Boolean)


366
# File 'lib/bitint/base.rb', line 366

def zero? = @int.zero?

#|(rhs) ⇒ Object

Bitwise ORs self and rhs.



470
# File 'lib/bitint/base.rb', line 470

def |(rhs) = new_instance(@int | rhs.to_int)

#~Object

Bitwise negates self.



450
# File 'lib/bitint/base.rb', line 450

def ~ = new_instance(~@int)