What's the difference between the tilde ~ and exclamation ! operators?

I’m new to Julia and trying to understand when to use ~ and when to use ! operators. The documentation describes each operator as:

! an exclamation mark is a prefix operator for logical negation (“not”)
~ the tilde is an operator for bitwise not

That reads like the same thing, said two different ways. Okay, so let’s try some things out and see if we can find some distinction between the two operators:

julia> !true
false

julia> ~true
false

Okay, at first glance, they both appear to do exactly the same thing. But when I re-read the definition of the ~, I see it mentions bitwise operation, so perhaps it works on boolean matrices:

julia> ~[true true false]
ERROR: MethodError: no method matching ~(::Matrix{Bool})

Nope, that didn’t work, because bitwise operation requires a dot before the ~:

julia> .~[true true false]
1×3 BitMatrix:
 0  0  1

Great, that worked. Now just out of curiosity, I try the same thing with .!:

julia> .![true true false]
1×3 BitMatrix:
 0  0  1

So in fact it appears that the “bitwise” distinction in the documentation for ~ shouldn’t suggest that its behavior is any different from !.

The definitions for ~ and ~ are almost identical to me, and their behavior appears to be the same, so what exactly is the difference between the ! and ~ operators?

3 Likes

If you check methods(!) you’ll see that this one is only defined for three types, namely Bool, Function and Missing

Whereas ~ is defined for a few more, including Bool but also all different Integer types.
That makes sense, because ~ inverts each bit of the variables on the bit level.

2 Likes

You don’t see the difference between ~ and ! when you apply it to a single bit (a Bool). However, ! can be applied only to boolean values, while ~ operates bitwise on any integer numbers (including boolean values):

julia> string(!0b01100110; base=2)
ERROR: MethodError: no method matching !(::UInt8)
Closest candidates are:
  !(::Function) at /usr/share/julia/base/operators.jl:1117
  !(::Bool) at /usr/share/julia/base/bool.jl:35
  !(::Missing) at /usr/share/julia/base/missing.jl:101
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

julia> string(~0b01100110; base=2)
"10011001"
6 Likes

Interesting. So is there ever a reason to use !?

When you operate on a boolean value.

2 Likes

When operating on a boolean value, what’s the advantage of ! over ~?

None, they’re identical:

But the distinction between ! for logical negation and ~ for bit twiddling is pretty common in many other languages.

10 Likes

The advantage is mostly just semantic, for the person reading your code, as logical and bitwise operations usually have different reasons to be done. The same reason you would write x÷2 instead of x>>1 although they are both the same (and the compiler would optimize the first into the second).

12 Likes

That’s very clear, thank you @giordano!

1 Like

Just a final complement, bitwise is different from element-wise. The former term is used for the individual bits inside a byte (or any number value/object), the latter is used more generically for any kind of elements inside any kind of container.

9 Likes

Oh, thank you for teaching me this distinction, @Henrique_Becker!

Precisely speaking that’s not correct, is it?
The ! has basically three cases where it can be used.

julia> methods(!)
# 3 methods for generic function "!":
[1] !(f::Function) in Base at operators.jl:1117
[2] !(x::Bool) in Base at bool.jl:35
[3] !(::Missing) in Base at missing.jl:101

julia> !missing
missing

Clarity of intent