bitint 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06dcd0b83a6c91e37dac7943bec5cbaaa91b901a6f8c4b19e6c45b4dba24269a
4
- data.tar.gz: b6299c4988582178484aee179e2a4aa2e8961f5aa87679378043fc1848aeea39
3
+ metadata.gz: c40be3354b42569bfb904d8dbbe9306f8f706688fd36264554cf8f872816af5b
4
+ data.tar.gz: d49b4008e93fdba7bbe61e55cdea7a8b4c67c8ce3fe72acfca78bbb2e7ea5838
5
5
  SHA512:
6
- metadata.gz: d536a9f415638dae60580af85aae14ae37f9ec1a03f8a25e988e66ebbe6b4c3055bd028f9b570fba1636adf4c0531218a68a672e8e113e0bd725e4f4bcea6d79
7
- data.tar.gz: 5438d936adca571ed12812580c69726e155aa786dccb349b908d1cc38a0f5f91b8703b0ec081ea52c10abd1c7fd7d279c4cae27b75f3fa27a6db62b39bdcb899
6
+ metadata.gz: 21eec32c4ee6bd2d0d0653d7301b3167d291493dc787e6cb2ace1c823560706745917dafe8dd535dca62471001cac012ed46fc7c415cbe03337e7c975f7d3618
7
+ data.tar.gz: 21df6c3d3736745f52990347c6be33b40a2b520f86949bb75fb8f54c0ecdf9aa25b51bdd2bee98a3b53ed5818102e4e46ae58be6b8c3998cb1107c733a862575
data/Gemfile CHANGED
@@ -8,3 +8,5 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "minitest", "~> 5.0"
11
+
12
+ gem "rbs-inline", "~> 0.12.0", require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bitint (0.7.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ logger (1.7.0)
10
+ minitest (5.26.0)
11
+ prism (1.6.0)
12
+ rake (13.3.0)
13
+ rbs (3.9.5)
14
+ logger
15
+ rbs-inline (0.12.0)
16
+ prism (>= 0.29)
17
+ rbs (>= 3.8.0)
18
+
19
+ PLATFORMS
20
+ arm64-darwin-23
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bitint!
25
+ minitest (~> 5.0)
26
+ rake (~> 13.0)
27
+ rbs-inline (~> 0.12.0)
28
+
29
+ BUNDLED WITH
30
+ 2.7.0.dev
data/Steepfile CHANGED
@@ -3,11 +3,11 @@ D = Steep::Diagnostic
3
3
  target :lib do
4
4
  signature "sig"
5
5
 
6
- # check "lib" # Directory name
7
- check "lib/bitint/bitint.rb"
6
+ check "lib" # Directory name
7
+ # check "lib/bitint/bitint.rb"
8
8
  # check "Gemfile" # File name
9
- check "app/models/**/*.rb" # Glob
10
9
  # ignore "lib/templates/*.rb"
10
+ ignore 'lib/bitint/refinements.rb'
11
11
 
12
12
  # library "pathname", "set" # Standard libraries
13
13
  # library "strong_json" # Gems
@@ -0,0 +1,552 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module BitInt
5
+ # @abstract Subclasses must be created via `Base.create`
6
+ class Base < Numeric
7
+ # :stopdoc:
8
+ # List of classes; used in `create` to ensure duplicate classes for bits and signed-ness aren't created
9
+ @classes = {} #: Hash[[Integer, bool], Class]
10
+ # :startdoc:
11
+
12
+ # @rbs self.@classes: Hash[[Integer, bool], Class]
13
+ # @rbs self.@bits: Integer
14
+ # @rbs self.@signed: bool
15
+ # @rbs @wrap: bool
16
+ # @rbs @int: Integer
17
+
18
+ # @rbs!
19
+ # BITS: Integer
20
+ # BYTES: Integer
21
+ # MASK: Integer
22
+ # ZERO: Base
23
+ # ONE: Base
24
+ # MIN: Base
25
+ # MAX: Base
26
+ # BOUNDS: Range[Base]
27
+
28
+ class << self
29
+
30
+ # Creates a new {BitInt::Base} subclass.
31
+ #
32
+ # @param bits [Integer?] the amount of bits the subclass should have. Must be at least +1+.
33
+ # @param bytes [Integer?] convenience argument; if supplied, sets +bits+ to +bytes * 8+. Cannot be used with +bits+
34
+ # @param signed [bool] Whether the subclass is a signed.
35
+ #
36
+ # @return [singleton(Base)] A subclass of {BitInt::Base}; subclasses are cached, so repeated calls return the same subclass.
37
+ #
38
+ # @raise [ArgumentError] When +bits+ is negative or zero
39
+ # @raise [ArgumentError] If both +bits+ and +bytes+ are supplied
40
+ #
41
+ # === Example
42
+ # puts BitInt::Base.create(bits: 8, signed: true).new(128) #=> -127
43
+ # puts BitInt::Base.create(bytes: 1, signed: false).new(256) #=> 0
44
+ #
45
+ # @rbs (bits: Integer, signed: bool) -> untyped
46
+ # | (bytes: Integer, signed: bool) -> untyped
47
+ def create(bits: nil, bytes: nil, signed:)
48
+ if bits.nil? == bytes.nil?
49
+ raise ArgumentError, "exactly one of 'bits' or 'bytes' must be supplied", caller(1)
50
+ end
51
+
52
+ bits ||= (_ = bytes) * 8
53
+
54
+ unless bits.positive?
55
+ raise ArgumentError, 'bit count must be positive', caller(1)
56
+ end
57
+
58
+ key = [bits, signed].freeze #: [Integer, bool]
59
+ @classes[key] ||= Class.new(Base) do |cls|
60
+ (_ = cls).setup!(bits, signed)
61
+ end
62
+ end
63
+
64
+ # :stopdoc:
65
+ # @rbs (Integer, bool) -> void
66
+ protected def setup!(bits, signed)
67
+ @bits = bits
68
+ @signed = signed
69
+
70
+ create = ->(int) {
71
+ allocate.tap { |x| x.instance_variable_set :@int, int }
72
+ }
73
+
74
+ class << self
75
+ public :new
76
+ end
77
+
78
+ const_set :BITS, @bits
79
+ const_set :BYTES, (@bits / 8.0).ceil
80
+ const_set :MASK, (1 << @bits).pred
81
+ const_set :ZERO, create[0]
82
+ const_set :ONE, create[1]
83
+ const_set :MIN, create[signed? ? -(1 << @bits.pred) : 0]
84
+ const_set :MAX, create[signed? ? (1 << @bits.pred).pred : (1 << @bits).pred]
85
+ const_set :BOUNDS, self::MIN .. self::MAX
86
+ end
87
+ # :startdoc:
88
+
89
+ # Returns whether this class represents a signed integer.
90
+ #
91
+ # @abstract Only defined on subclasses
92
+ #
93
+ # === Example
94
+ # puts BitInt::U8.signed? #=> false
95
+ # puts BitInt::I8.signed? #=> true
96
+ #
97
+ # @rbs () -> bool
98
+ def signed? = @signed
99
+
100
+ # Returns whether this class represents an unsigned integer.
101
+ #
102
+ # @abstract Only defined on subclasses
103
+ #
104
+ # === Example
105
+ # puts BitInt::U8.unsigned? #=> true
106
+ # puts BitInt::I8.unsigned? #=> false
107
+ #
108
+ # @rbs () -> bool
109
+ def unsigned? = !signed?
110
+
111
+ # Returns a debugging string representation of this class
112
+ #
113
+ # If the class is one of the builtins (eg +BitInt::U16+), it uses its name
114
+ # as the string, otherwise it uses a debugging representation
115
+ #
116
+ # === Example
117
+ # p BitInt::U16 #=> BitInt::U16
118
+ # p BitInt::U(17) #=> #<BitInt::Base @bits=17 @signed=false>
119
+ #
120
+ # @rbs () -> String
121
+ def inspect = name || "#<BitInt::Base @bits=#@bits @signed=#@signed>"
122
+ alias to_s inspect
123
+
124
+ # Wraps an +integer+ to be within the bounds of +self+
125
+ #
126
+ # @param integer [Integer] the integer to wrap
127
+ # @return [Integer] an integer guaranteed to be within +self::BOUNDS+.
128
+ #
129
+ # @abstract Only defined on subclasses
130
+ #
131
+ # === Example
132
+ # puts BitInt::I8.wrap(127) #=> 127
133
+ # puts BitInt::I8.wrap(128) #=> -128
134
+ # puts BitInt::I8.wrap(0xFF_FF_FF_FF_FF) #=> -1
135
+ #
136
+ # @rbs (Integer) -> Integer
137
+ def wrap(integer)
138
+ ((integer - self::MIN.to_i) & self::MASK) + self::MIN.to_i
139
+ end
140
+
141
+ # Check to see if +integer+ is in bounds for +self+
142
+ #
143
+ # @abstract Only defined on subclasses
144
+ #
145
+ # @param integer [Integer] the integer to check
146
+ # @return [bool] whether the integer is in bounds
147
+ #
148
+ # === Example
149
+ # puts BitInt::I16.in_bounds?(32767) #=> true
150
+ # puts BitInt::I16.in_bounds?(32768) #=> false
151
+ # puts BitInt::U32.in_bounds?(-1) #=> false
152
+ #
153
+ # @rbs (Integer) -> bool
154
+ def in_bounds?(integer)
155
+ # TODO: use `self::BOUNDS`
156
+ (self::MIN.to_i .. self::MAX.to_i).include? integer
157
+ end
158
+ end
159
+
160
+ # Creates a new instance with the +integer+.
161
+ #
162
+ # @param integer [Integer] the integer to use
163
+ # @param wrap [bool] changes how {Base.in_bounds? out-of-bounds} integers are handled. When true,
164
+ # they're {Base.wrap wrapped}; when false, an +OverflowError+ to be raised.
165
+ # @raise [OverflowError] raised when +wrap+ is +false+ and +integer+ is out of bounds.
166
+ #
167
+ # === Example
168
+ # puts BitInt::U8.new(27) #=> 27
169
+ # puts BitInt::U8.new(-1) #=> 255
170
+ # puts BitInt::U8.new(-1, wrap: false) #=> OverflowError
171
+ # puts BitInt::I8.new(255) #=> -1
172
+ #
173
+ # @rbs (Integer, ?wrap: boolish) -> void
174
+ def initialize(integer, wrap: true)
175
+ unless wrap || self.class.in_bounds?(integer)
176
+ exc = OverflowError.new(integer, self.class::BOUNDS)
177
+ exc.set_backtrace(
178
+ caller_locations(1) #: Array[Thread::Backtrace::Location]
179
+ )
180
+ raise exc
181
+ end
182
+
183
+ @int = self.class.wrap(integer)
184
+ @wrap = wrap
185
+ end
186
+
187
+ ################################################################################################
188
+ # Conversions #
189
+ ################################################################################################
190
+
191
+ # :section: Conversions
192
+
193
+ # Returns the underlying integer.
194
+ #
195
+ # @rbs () -> Integer
196
+ def to_i = @int
197
+ alias to_int to_i
198
+
199
+ # Converts +self+ to a +Float+.
200
+ #
201
+ # @rbs () -> Float
202
+ def to_f = @int.to_f
203
+
204
+ # Converts +self+ to a +Rational+.
205
+ #
206
+ # @rbs () -> Rational
207
+ def to_r = @int.to_r
208
+
209
+ # (no need for `.to_c` as `Numeric` defines it)
210
+
211
+ # Converts +self+ to a +String+.
212
+ #
213
+ # If no +base+ is given, it just returns a normal String in base 10.
214
+ # If a base is given, a string padded with `0`s will be returned.
215
+ #
216
+ # === Examples
217
+ # puts BitInt::U16.new(1234).to_s #=> 1234
218
+ # puts BitInt::U16.new(1234).to_s(16) #=> 04d2
219
+ #
220
+ # @rbs (?int? base) -> String
221
+ def to_s(base = nil)
222
+ return @int.to_s unless base
223
+ base = base.to_int
224
+
225
+ adjusted = negative? ? (-2*self.class::MIN.to_i + @int).to_i : @int
226
+ adjusted.to_s(base).rjust(self.class::BITS / Math.log2(base), negative? ? '1' : '0')
227
+ end
228
+ alias inspect to_s
229
+
230
+ # Returns a base-16 string of +self+. Equivalent to +to_s(16)+.
231
+ #
232
+ # If +upper: true+ is passed, returns an upper-case version.
233
+ #
234
+ # === Examples
235
+ # puts BitInt::U16.new(1234).hex #=> 04d2
236
+ # puts BitInt::U16.new(1234, upper: true).hex #=> 04D2
237
+ #
238
+ # @rbs (?upper: boolish) -> String
239
+ def hex(upper: false) = to_s(16).tap { _1.upcase! if upper }
240
+
241
+ # Returns a base-8 string of +self+. Equivalent to +to_s(8)+.
242
+ #
243
+ # === Example
244
+ # puts BitInt::U16.new(12345).oct #=> 30071
245
+ #
246
+ # @rbs () -> String
247
+ def oct = to_s(8)
248
+
249
+ # Returns a base-2 string of +self+. Equivalent to +to_s(2)+.
250
+ #
251
+ # === Example
252
+ # puts BitInt::U16.new(54321).bin #=> 0000010011010010
253
+ #
254
+ # @rbs () -> String
255
+ def bin = to_s(2)
256
+
257
+ # Converts +other+ to an instance of +self+, and returns a tuple of +[<converted>, self]+
258
+ #
259
+ # @rbs (int) -> [instance, self]
260
+ def coerce(other) = [new_instance(other.to_int), self]
261
+
262
+ ################################################################################################
263
+ # ... #
264
+ ################################################################################################
265
+
266
+ # Checks to see if +rhs+ is equal to +selF+
267
+ #
268
+ # === Example
269
+ # U64 = BitInt::U(64)
270
+ # twelve = U64.new(12)
271
+ #
272
+ # # Behaves as you'd expect.
273
+ # puts twelve == U64.new(12) #=> true
274
+ # puts twelve == U64.new(13) #=> false
275
+ # puts twelve == 12 #=> true
276
+ # puts twelve == 12.0 #=> true
277
+ # puts twelve == 13 #=> false
278
+ # puts twelve == Object.new #=> false
279
+ #
280
+ # @rbs (untyped) -> boolish
281
+ def ==(rhs)
282
+ defined?(rhs.to_i) && @int == rhs.to_i
283
+ end
284
+
285
+ # Checks to see if +rhs+ is another +BitInt::Base+ of the same class, and
286
+ # have the same contents.
287
+ #
288
+ # @rbs (untyped) -> bool
289
+ def eql?(rhs)
290
+ rhs.is_a?(self.class) && @int == rhs.to_i
291
+ end
292
+
293
+ # Returns a hash code for this class.
294
+ #
295
+ # @rbs () -> Integer
296
+ def hash = [self.class, @int].hash
297
+
298
+ # Always return +true+, as +BitInt::Base+s are always integers.
299
+ #
300
+ # @rbs () -> true
301
+ def integer? = true
302
+
303
+ # @rbs (Integer, ?wrap: bool) -> instance
304
+ private def new_instance(int, wrap: @wrap) = self.class.new(int, wrap:)
305
+
306
+ ################################################################################################
307
+ # Math #
308
+ ################################################################################################
309
+
310
+ # Numerically negates +self+.
311
+ #
312
+ # @rbs () -> instance
313
+ def -@ = new_instance(-@int)
314
+
315
+ # Compares +self+ to +rhs+.
316
+ #
317
+ # @rbs (_ToI) -> (-1 | 0 | 1)
318
+ # | (untyped) -> Integer?
319
+ def <=>(rhs) = defined?(rhs.to_i) ? @int <=> (_ = rhs).to_i : nil
320
+
321
+ # Adds +self+ to +rhs+.
322
+ #
323
+ # @rbs (int) -> instance
324
+ def +(rhs) = new_instance(@int + rhs.to_int)
325
+
326
+ # Subtracts +rhs+ from +self+.
327
+ #
328
+ # @rbs (int) -> instance
329
+ def -(rhs) = new_instance(@int - rhs.to_int)
330
+
331
+ # Multiplies +self+ by +rhs+.
332
+ #
333
+ # @rbs (int) -> instance
334
+ def *(rhs) = new_instance(@int * rhs.to_int)
335
+
336
+ # Divides +self+ by +rhs+.
337
+ #
338
+ # @rbs (int) -> instance
339
+ def /(rhs) = new_instance(@int / rhs.to_int)
340
+
341
+ # Modulos +self+ by +rhs+.
342
+ #
343
+ # @rbs (int) -> instance
344
+ def %(rhs) = new_instance(@int % rhs.to_int)
345
+
346
+ # Raises +self+ to the +rhs+th power.
347
+ #
348
+ # @rbs (int) -> instance
349
+ def **(rhs) = new_instance((@int ** rhs.to_int).to_int) # TODO: use `Integer#pow(int, int)`
350
+
351
+ # :section:
352
+
353
+ # Returns whether +self+ is a positive integer. Zero is not positive.
354
+ #
355
+ # @rbs () -> bool
356
+ def positive? = @int.positive?
357
+
358
+ # Return whether +self+ is a negative integer. Zero is not negative.
359
+ #
360
+ # @rbs () -> bool
361
+ def negative? = @int.negative?
362
+
363
+ # Returns whether +self+ is zero.
364
+ #
365
+ # @rbs () -> bool
366
+ def zero? = @int.zero?
367
+
368
+ # Returns a falsey value if zero, otherwise returns +self+.
369
+ #
370
+ # @rbs () -> self?
371
+ def nonzero? = @int.nonzero? && self
372
+
373
+ # Checks to see if +self+ is even.
374
+ #
375
+ # @rbs () -> bool
376
+ def even? = @int.even?
377
+
378
+ # Checks to see if +self+ is odd.
379
+ #
380
+ # @rbs () -> bool
381
+ def odd? = @int.odd?
382
+
383
+ # Same as +Integer#times+, but returns instances of +self+.
384
+ #
385
+ # @rbs () -> Enumerator[instance, self]
386
+ # | () { (instance) -> void } -> self
387
+ def times
388
+ return to_enum(_ = __method__) unless block_given?
389
+
390
+ @int.times do |int|
391
+ yield new_instance int
392
+ end
393
+
394
+ self
395
+ end
396
+
397
+ # Same as +Integer#downto+, but returns instances of +self+.
398
+ #
399
+ # @rbs (Numeric) -> Enumerator[instance, self]
400
+ # | (Numeric) { (instance) -> void } -> self
401
+ def downto(what)
402
+ return to_enum(_ = __method__) unless block_given?
403
+
404
+ @int.downto what do |int|
405
+ yield new_instance int
406
+ end
407
+
408
+ self
409
+ end
410
+
411
+ # Same as +Integer#downto+, but returns instances of +self+.
412
+ #
413
+ # @rbs (Numeric) -> Enumerator[instance, self]
414
+ # | (Numeric) { (instance) -> void } -> self
415
+ def upto(what)
416
+ return to_enum(_ = __method__) unless block_given?
417
+
418
+ @int.upto what do |int|
419
+ yield new_instance int
420
+ end
421
+
422
+ self
423
+ end
424
+
425
+ # Gets the next value, or throws a {OverFlowError} if at the top.
426
+ #
427
+ # @rbs () -> instance
428
+ def succ
429
+ # TODO: this changes `@wrap`; that's weird
430
+ new_instance(@int + 1, wrap: false)
431
+ end
432
+
433
+ # Gets the next value, or throws a {OverFlowError} if at the top.
434
+ #
435
+ # @rbs () -> instance
436
+ def pred
437
+ # TODO: this changes `@wrap`; that's weird
438
+ new_instance(@int - 1, wrap: false)
439
+ end
440
+
441
+ ## TODO: SUCC
442
+
443
+ ################################################################################################
444
+ # Bitwise Methods #
445
+ ################################################################################################
446
+
447
+ # Bitwise negates +self+.
448
+ #
449
+ # @rbs () -> instance
450
+ def ~ = new_instance(~@int)
451
+
452
+ # Shifts +self+ left by +rhs+ bits.
453
+ #
454
+ # @rbs (int) -> instance
455
+ def <<(rhs) = new_instance(@int << rhs.to_int)
456
+
457
+ # Shifts +self+ right by +rhs+ bits.
458
+ #
459
+ # @rbs (int) -> instance
460
+ def >>(rhs) = new_instance(@int >> rhs.to_int)
461
+
462
+ # Bitwise ANDs +self+ and +rhs+.
463
+ #
464
+ # @rbs (int) -> instance
465
+ def &(rhs) = new_instance(@int & rhs.to_int)
466
+
467
+ # Bitwise ORs +self+ and +rhs+.
468
+ #
469
+ # @rbs (int) -> instance
470
+ def |(rhs) = new_instance(@int | rhs.to_int)
471
+
472
+ # Bitwise XORs +self+ and +rhs+.
473
+ #
474
+ # @rbs (int) -> instance
475
+ def ^(rhs) = new_instance(@int ^ rhs.to_int)
476
+
477
+ # Gets the bit at index +idx+ or returns +nil+.
478
+ #
479
+ # This is equivalent to +Integer#[]+
480
+ # @rbs (int, ?int) -> Integer?
481
+ # | (range[int]) -> Integer?
482
+ def [](...) = __skip__ = @int.[](...)
483
+
484
+ # Returns true if any bit in `mask` is set in +self+.
485
+ #
486
+ # @rbs (int) -> bool
487
+ def anybits?(mask) = @int.anybits?(mask)
488
+
489
+ # Returns true if all bits in `mask` are set in +self+.
490
+ #
491
+ # @rbs (int) -> bool
492
+ def allbits?(mask) = @int.allbits?(mask)
493
+
494
+ # Returns true if no bits in `mask` are set in +self+.
495
+ #
496
+ # @rbs (int) -> bool
497
+ def nobits?(mask) = @int.nobits?(mask)
498
+
499
+ # Returns the amount of bits required to represent +self+
500
+ #
501
+ # Unlike +Integer#bit_length+, this never changes and is equivalent
502
+ # to +self.class::BITS+.
503
+ #
504
+ # @rbs () -> Integer
505
+ def bit_length = self.class::BITS
506
+
507
+ # Returns the amount of bytes required to represent +self+
508
+ #
509
+ # This is equivalent to +self.class::BYTES+
510
+ #
511
+ # @rbs () -> Integer
512
+ def byte_length = self.class::BYTES
513
+
514
+ # Returns all the bytes that're used represent +self+
515
+ #
516
+ # @rbs (?:native | :big | :little) -> Array[U8]
517
+ def bytes(endian = :native)
518
+ each_byte(endian).to_a
519
+ end
520
+
521
+ # Executes the block once for each byte in +self+.
522
+ #
523
+ # Bytes are converted to +U8+. If no block is given, returns an +Enumerator+
524
+ #
525
+ # @rbs (?:native | :big | :little) { (U8) -> void } -> self
526
+ # | (?:native | :big | :little) -> Enumerator[U8, self]
527
+ def each_byte(endian = :native)
528
+ return to_enum(_ = __method__, endian) unless block_given?
529
+
530
+ template = '_CS_L___Q'[self.class::BYTES]
531
+ if template.nil? || template == '_'
532
+ raise ArgumentError, 'bytes only works for sizes of 8, 16, 32, or 64.'
533
+ end
534
+
535
+ template.downcase! if self.class.signed?
536
+
537
+ case endian
538
+ when :native then # do nothing
539
+ when :little then template.concat '<' unless self.class::BYTES == 1
540
+ when :big then template.concat '>' unless self.class::BYTES == 1
541
+ else
542
+ raise ArgumentError, 'endian must be :big, :little, or :native'
543
+ end
544
+
545
+ [@int].pack(template).unpack('C*').each do |byte| #: Integer
546
+ yield U8.new(byte)
547
+ end
548
+
549
+ self
550
+ end
551
+ end
552
+ end