11

I was goofing around in a pry REPL and found some very interesting behavior: the tilde method.

It appears Ruby syntax has a built-in literal unary operator, ~, just sitting around.

This means ~Object.new sends the message ~ to an instance of Object:

class Object
  def ~
    puts 'what are you doing, ruby?'
  end
end
~Object.new #=> what are you doing, ruby?

This seems really cool, but mysterious. Is Matz essentially trying to give us our own customizable unary operator?

The only reference I can find to this in the rubydocs is in the operator precedence notes, where it's ranked as the number one highest precedence operator, alongside ! and unary + This makes sense for unary operators. (For interesting errata about the next two levels of precedence, ** then unary -, check out this question.) Aside from that, no mention of this utility.

The two notable references to this operator I can find by searching, amidst the ~=,!~, and~>` questions, are this and this. They both note its usefulness, oddity, and obscurity without going into its history.

After I was about to write off ~ as a cool way to provide custom unary operator behavior for your objects, I found a place where its actually used in ruby--fixnum (integers).

~2 returns -3. ~-1 returns 1. So it negates an integer and subtracts one... for some reason?

Can anyone enlighten me as purpose of the tilde operator's unique and unexpected behavior in ruby at large?

Community
  • 1
  • 1
Chris Keele
  • 3,364
  • 3
  • 30
  • 52

5 Answers5

5

Using pry to inspect the method:

show-method 1.~

From: numeric.c (C Method):
Owner: Fixnum
Visibility: public
Number of lines: 5

static VALUE
fix_rev(VALUE num)
{
    return ~num | FIXNUM_FLAG;
}

While this is impenetrable to me, it prompted me to look for a C unary ~ operator. One exists: it's the bitwise NOT operator, which flips the bits of a binary integer (~1010 => 0101). For some reason this translates to one less than the negation of a decimal integer in Ruby.

More importantly, since ruby is an object oriented language, the proper way to encode the behavior of ~0b1010 is to define a method (let's call it ~) that performs bitwise negation on a binary integer object. To realize this, the ruby parser (this is all conjecture here) has to interpret ~obj for any object as obj.~, so you get a unary operator for all objects.

This is just a hunch, anyone with a more authoritative or elucidating answer, please enlighten me!

--EDIT--

As @7stud points out, the Regexp class makes use of it as well, essentially matching the regex against $_, the last string received by gets in the current scope.

As @Daiku points out, the bitwise negation of Fixnums is also documented.

I think my parser explanation solves the bigger question of why ruby allows ~ as global unary operator that calls Object#~.

Chris Keele
  • 3,364
  • 3
  • 30
  • 52
4

For fixnum, it's the one's complement, which in binary, flips all the ones and zeros to the opposite value. Here's the doc: http://www.ruby-doc.org/core-2.0/Fixnum.html#method-i-7E. To understand why it gives the values it does in your examples, you need to understand how negative numbers are represented in binary. Why ruby provides this, I don't know. Two's complement is generally the one used in modern computers. It has the advantage that the same rules for basic mathematical operations work for both positive and negative numbers.

Daiku
  • 1,237
  • 11
  • 20
2

The ~ is the binary one's complement operator in Ruby. One's complement is just flipping the bits of a number, to the effect that the number is now arithmetically negative.

For example, 2 in 32-bit (the size of a Fixnum) binary is 0000 0000 0000 0010, thus ~2 would be equal to 1111 1111 1111 1101 in one's complement.

However, as you have noticed and this article discusses in further detail, Ruby's version of one's complement seems to be implemented differently, in that it not only makes the integer negative but also subtracts 1 from it. I have no idea why this is, but it does seem to be the case.

Daniel Brady
  • 934
  • 3
  • 11
  • 27
  • 1
    I think the documentation has to be fixed, because `~` operator does not makes *one's* complement but *two's*. See [Two's complement on Wikipedia](http://en.wikipedia.org/wiki/Two%27s_complement). – Torimus Jul 27 '13 at 09:54
  • That's exactly what I thought when I started messing around with it myself! However, isn't two's complement flipping the bits and _adding_ one, not subtracting one?? – Daniel Brady Jul 27 '13 at 17:05
  • "I think the documentation has to be fixed..." What documentation? Everything looks fine here. That article linked in the answer is rubbish. "Thus, the ~ operator in Ruby means {-x - 1} instead of {~x}." is completely wrong. `~x` is the same in ruby as any language: a bitwise complement. Ruby uses two's complement (like everyone else) so that it can represent one more number than one's complement, and this means that a bitwise complement of a number has a different magnitude than the original number. Everything is fine here. – lmat - Reinstate Monica Jun 11 '20 at 09:27
  • 1
    ‍♂️ I don’t remember what documentation I was referring to, it was 8 years ago. At that time, I would have bad maybe a month’s experience with Ruby, so I was probably referring to the official Ruby documentation of the ~ operator at the time. – Daniel Brady Mar 12 '21 at 01:40
  • 1
    This other answer about the ~ bitwise operator seems to explain better: bitwise complement is a one’s complement as we’ve discussed here, but there’s an interesting interaction when it comes to negative numbers because of how they are stored: https://stackoverflow.com/a/791340/1795402 – Daniel Brady Mar 12 '21 at 01:42
0

It's mentioned in several places in pickaxe 1.8, e.g. the String class. However, in ruby 1.8.7 it doesn't work on the String class as advertised. It does work for the Regexp class:

print "Enter something: "
input = gets
pattern = 'hello'
puts ~ /#{pattern}/

--output:--
Enter something: 01hello
2

It is supposed to work similarly for the String class.

7stud
  • 46,922
  • 14
  • 101
  • 127
0

Each of these are documented in the documentation.

This list is from the documentation for Ruby 2.6

The behavior of this method "at large" is basically anything you want it to be, as you described yourself with your definition of a method called ~ on Object class. The behaviors on the core classes that have it defined by the implementations maintainers, seems to be pretty well documented, so that it should not have unexpected behavior for those objects.

Dorian
  • 7,749
  • 4
  • 38
  • 57
vgoff
  • 10,980
  • 3
  • 38
  • 56
  • Unsure why the down vote 6 years after. But yeah, it maybe a little out of date. ;) – vgoff Sep 10 '19 at 00:49
  • I think it's because you were referencing documentation but not linking to it – Dorian Jul 28 '22 at 08:52
  • Oh, that could be, since I was referencing the documentation that is generated on my system, rather than needing to go out to some remote system to get to it. (And it guarantees that the documentation is applicable to my installed version.) Thanks for the edit. – vgoff Jul 29 '22 at 21:17
  • @Dorian if you would like to either link to the documentation from Ruby 2.6, or update it, and set the links all to the same version of documentation, that would be very helpful. – vgoff Jul 29 '22 at 21:20