Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions hugr-py/src/hugr/std/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,39 @@
INT_T = int_t(5)


def _to_unsigned(val: int, bits: int) -> int:
"""Convert a signed integer to its unsigned representation
in twos-complement form.
Comment on lines +56 to +57
Copy link
Collaborator

@aborgna-q aborgna-q Jul 7, 2025

Choose a reason for hiding this comment

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

Mention that we are wrapping the negative values.

Otherwise, the "unsigned representation of a signed value" is undefined for negatives.


Positive integers are unchanged, while negative integers
are converted by adding 2^bits to the value.

Raises ValueError if the value is out of range for the given bit width
(valid range is [-2^(bits-1), 2^(bits-1)-1]).
"""
half_max = 1 << (bits - 1)
min_val = -half_max
max_val = half_max - 1
if val < min_val or val > max_val:
msg = f"Value {val} out of range for {bits}-bit signed integer."
raise ValueError(msg) #

if val < 0:
return (1 << bits) + val
return val


@dataclass
class IntVal(val.ExtensionValue):
"""Custom value for an integer."""
"""Custom value for a signed integer."""

v: int
width: int = field(default=5)

def to_value(self) -> val.Extension:
name = "ConstInt"
payload = {"log_width": self.width, "value": self.v}
unsigned = _to_unsigned(self.v, 1 << self.width)
payload = {"log_width": self.width, "value": unsigned}
return val.Extension(
name,
typ=int_t(self.width),
Expand All @@ -72,8 +95,9 @@
return f"{self.v}"

def to_model(self) -> model.Term:
unsigned = _to_unsigned(self.v, 1 << self.width)

Check warning on line 98 in hugr-py/src/hugr/std/int.py

View check run for this annotation

Codecov / codecov/patch

hugr-py/src/hugr/std/int.py#L98

Added line #L98 was not covered by tests
return model.Apply(
"arithmetic.int.const", [model.Literal(self.width), model.Literal(self.v)]
"arithmetic.int.const", [model.Literal(self.width), model.Literal(unsigned)]
)


Expand Down
38 changes: 38 additions & 0 deletions hugr-py/tests/test_prelude.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import pytest

from hugr.build.dfg import Dfg
from hugr.std.int import IntVal, int_t
from hugr.std.prelude import STRING_T, StringVal

from .conftest import validate
Expand All @@ -16,3 +19,38 @@ def test_string_val():
dfg.set_outputs(v)

validate(dfg.hugr)


@pytest.mark.parametrize(
("log_width", "v", "unsigned"),
[
(5, 1, 1),
(4, 0, 0),
(6, 42, 42),
(2, -1, 15),
(1, -2, 2),
(3, -23, 233),
(3, -256, None),
(2, 16, None),
],
)
def test_int_val(log_width: int, v: int, unsigned: int | None):
val = IntVal(v, log_width)
if unsigned is None:
with pytest.raises(
ValueError,
match=f"Value {v} out of range for {1<<log_width}-bit signed integer.",
):
val.to_value()
return
ext_val = val.to_value()

assert ext_val.name == "ConstInt"
assert ext_val.typ == int_t(log_width)
assert ext_val.val == {"log_width": log_width, "value": unsigned}

dfg = Dfg()
o = dfg.load(val)
dfg.set_outputs(o)

validate(dfg.hugr)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading