Skip to content

Implement bitwise operators #755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open

Implement bitwise operators #755

wants to merge 11 commits into from

Conversation

rehelmin
Copy link

The code in this pull request adds implementations for the following bitwise operators:

  • And
  • Or
  • Xor
  • Not
  • Bit shift left
  • Bit shift right

Note that typically used '^' character for xor currently clashes with the power operator in numbat, so I have used the more cumbersome to type, but unique ⨁ character for performing the xor operation.

@Goju-Ryu
Copy link
Collaborator

Note that typically used '^' character for xor currently clashes with the power operator in numbat, so I have used the more cumbersome to type, but unique ⨁ character for performing the xor operation.

That sounds like a reasonable solution. I think it would be good, having an xor keyword in addition, to make it less cumbersome.

@rehelmin
Copy link
Author

Updated. I'm not sure why all the builds are all failing now though? I'm able to build it just fine on my workstation.

@Goju-Ryu
Copy link
Collaborator

Thank you. I think the failing builds are related to #752. It should be an easy fix, which I can look into tonight.

@Goju-Ryu
Copy link
Collaborator

I fixed the error, added documentation and updated the add-in.
I also changed the tokenizer implementation slightly to use the keywords map instead of custom parsing for the xor keyword.

type Output = Number;

fn shl(self, rhs: Self) -> Self::Output {
Number(((self.0 as i64) << rhs.0 as i64) as f64)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this!

I believe we need to write a few more test cases for extreme inputs, and we need to improve error handling here. These bitwise operations can overflow, and we don't want Numbat to crash in those cases (but rather emit some error, similar to the division-by-zero error). I'm also not sure about these as casts...

For example:

>>> 1 << 63

  1 << 63

    = -9.22337e+18

>>> 1 << 64

thread 'main' panicked at numbat/src/number.rs:167:16:
attempt to shift left with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure about these as casts...

What aspect of them are you unsure about? It was the only way I could figure out how to work around the fact that the underlying primitive type for Number is f64. None of the bitwise operations make sense in the context of an f64 type.

Good point on the overflowing. Looks like there are a bunch of different options that have different behavior defined for overflows that could occur during the shifting operations:

  • unbounded: Any overflowing bits are dropped. If the rhs is larger than the type being shifted then all the bits are dropped resulting in a value of 0.
  • wrapping: Masks off the higher order rhs bits that would cause an overflow to occur.
  • checked: Returns None if the size of the rhs is larger than the size of the type being shifted.

My preference is the unbounded implementation, but I could be convinced that it would be better to throw an error rather than to silently fail in a defined way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth having more than one implementation, but I agree with preferring the unbounded version for the operator. The other versions could potentially be added as ffi functions to give the option if needed.

Disclaimer: I don't use bit shifting that much, so my preference for unbounded shifts is mostly just that they make more intuitive sense to me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One side effect of the type casts is that it silently truncates the decimal part of any floats that someone tries to perform a bitwise oepration on. For example 5.5 | 2.1 silently truncates the .5 and .1 and gives the value of 7. I can see this being somewhat undesired. We could probably add some error checking that only allows bitwise operations to be performed when both arguments are scalars. I've already added this error checking for performing the bitwise not. I don't think it would be a big step to add it to the rest of the operations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth having more than one implementation, but I agree with preferring the unbounded version for the operator. The other versions could potentially be added as ffi functions to give the option if needed.

I would definitely need some help with the ffi function implementation. These updates are my first forays into the world of rust, so I've mostly been pattern matching off of existing code to implement them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, another point on the type casts is that it doesn't make sense to bit shift with a negative integer on the right-hand side of the operation. Rust throws an error in this scenario. I can fix that tonight.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One side effect of the type casts is that it silently truncates the decimal part of any floats that someone tries to perform a bitwise oepration on. For example 5.5 | 2.1 silently truncates the .5 and .1 and gives the value of 7.

Yeah that would not be ideal.

We could probably add some error checking that only allows bitwise operations to be performed when both arguments are scalars.

This sounds like a good idea to me, but I think this decision should be up to @sharkdp.

I would definitely need some help with the ffi function implementation.

No problem, I think I can help with that. I’m not sure when I can, but I'll drop some updates with the ffi connections and a todo for where the function implementation should go if you want it.

These updates are my first forays into the world of rust, so I've mostly been pattern matching off of existing code to implement them.

I’m learning rust in much the same way. Thank you for taking on the challenge to add this feature 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a very simple ffi outline that should work for the left and right shifts. I've left comments with what still needs to be done before it's done.

Ryan Helminen and others added 3 commits July 19, 2025 10:56
Performing a bitwise operation on a float with a fractional value is
undefined behavior. If it looks like an integer, it is safe to type cast.
If the number value has a fractional component, it is likely better to
bail out with a runtime error rather than silently truncating the fractional
part.
Overflows during bit shift now are handled using the unbounded_shl approach.
Bit shifts with negative values on the rhs of the operation also now
report a runtime error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants