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. 
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 