0_u64|someInt32

Hello everyone!

I’m trying to convert some numbers and I don’t get the results I would expect. I hope someone can explain to me, what I’m doing wrong and why I I don’t get the result I want.

My code looks like this:

module Foo
  def foo
    0_u64|self
  end
end

struct Int64
  include Foo
end

struct Int32
  include Foo
end

struct Int8
  include Foo
end

p (-1_i64).foo
p (-1_i32).foo
p (-1_i8).foo

And I receive this output:

18446744073709551615 # fits my expectation
18446744073709551615 # I would have expected 4294967295_u64
18446744073709551615 # I would have expected 255_u64

My (probably wrong) idea was:
-1_i8 would be equal to the bits 11111111, and
0000000000000000000000000000000000000000000000000000000000000000 | 11111111 should result in
0000000000000000000000000000000000000000000000000000000011111111 (or 255 in decimal representation).

Question 1:
Why isn’t it like that? I hopefully might avoid other mistakes once I understand why this didn’t work out.

Question 2:
What would be the best (as in primarily most performant, secondarily lowest code efforts) way to get the wanted results?
It works when I do

struct Int32
  def foo
    0_u64|(0_u32|self)
  end
end

struct Int8
  def foo
    0_u64|(0_u8|self)
  end
end

but I would prefer a more generic approach.

Thanks in advance!

Whenever there’s a math operation between two types, the result has the type of the left operand. The right operand is casted to the left operand’s type. At least that’s how we decided it should work in Crystal.

2 Likes

Yes, I figured that already out, and that’s fine.
But this doesn’t explain the unexpected result. Or maybe I’m just not seeing it (but this is still the same for the “working method” as well!?)

The type fits absolutely to my expectation. I’m surprised (and confused) by its value.

Okay - this is likely the explanation. I missed the upfront casting. Thanks.

Should anyone else want to do the same - this works for me:

module Foo
  def foo
    x = self # needed to workaround #Error: can't take address of self 
    ((UInt64::MAX>>(8-sizeof(typeof(x)))*8))&pointerof(x).as(Pointer(UInt64)).value
  end
end

Um. That make no sense. What are you actually trying to achieve here?

p (-1_i64).foo       # I want 18446744073709551615_u64
p (-1_i32).foo       # I want 4294967295_u64
p (-1_i8).foo        # I want 255_u64

And this works fine now. :grinning:

Purpose/ intention is, to then do 7bit encoding on the UInt64 to send it over a socket, so a C# application, which expects the other side to send data via .Net’s BinaryWriter.Write7BitEncodedInt, can understand it. The stuff above was necessary for those cases when the value is negative.

And as this will now show up if someone searches for 7BitEncodedInt - here is the full method in case someone needs it:

  def serialize7 (io)
    x = self
    v= ((UInt64::MAX>>(8-sizeof(typeof(x)))*8))&pointerof(x).as(Pointer(UInt64)).value
    until v<0x80
      io.write_byte (v%0x80+0x80).to_u8
      v>>= 7
    end
    io.write_byte v.to_u8
  end

(If someone is going to use it: be aware my use case requires only values up to 64bit - you might need more bits though and probably have to adjust the v=… line)

The bitwise OR is not suitable for integer casts like this. Instead one could do:

struct Int32
  def to_unsigned!
    to_u32!
  end
end

struct Int64
  def to_unsigned!
    to_u64!
  end
end

# ditto for the rest of `Int::Signed`

def serialize7(io : IO, value : Int)
  if value >= 0
    while value > 0x7F
      io.write_byte((value & 0x7F).to_u8! | 0x80_u8)
      value >>= 7
    end
    io.write_byte(value.to_u8!)
  elsif value.is_a?(Int::Signed)
    serialize7(io, value.to_unsigned!) # size-preserving integer sign cast
  else
    # negative `BigInt`s go here
  end
end

In fact I have had a need for #to_unsigned! somewhere else in the standard library.

2 Likes

Didn’t get it the very first moment. But yes, this works. Thanks!

But it’s a lot more code, which needs to be adjusted for each type. My last solution fits all types, except the big ones, which I fortunately don’t need, so I’m probably go and stick with that (probably go rid of the u64, as I can’t remember any good reason I would have needed it for in the first place; but maybe there was and I just can’t remember it). Edit 2: went basically with your solution (it 's not much code, and it’s quickly done and won’t be touched again anyway, so yeah, why not)

bitwise OR

You are referring to my very first approach when I started this topic, aren’t you? Yeah, I didn’t know that there is an automatic cast happening before the OR, but @asterite solved this part of my confusion already.

Anyway, thanks a lot :slightly_smiling_face: