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
27 changes: 20 additions & 7 deletions src/fable-library-py/src/floats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ macro_rules! float_variant {
// --- Arithmetic Methods ---
// Fast path: check if other is our type first to avoid __float__ call
pub fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
// Fast path: other is same type
if let Ok(other_val) = other.extract::<Self>() {
return Ok(Self(self.0 + other_val.0));
// Fast path: other is same type (cast avoids a discarded PyErr on miss)
if let Ok(other_val) = other.cast::<Self>() {
return Ok(Self(self.0 + other_val.get().0));
}
// Slow path: extract as primitive
let other_val = other.extract::<$type>()?;
Expand All @@ -63,8 +63,8 @@ macro_rules! float_variant {
}

pub fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(other_val) = other.extract::<Self>() {
return Ok(Self(self.0 - other_val.0));
if let Ok(other_val) = other.cast::<Self>() {
return Ok(Self(self.0 - other_val.get().0));
}
let other_val = other.extract::<$type>()?;
Ok(Self(self.0 - other_val))
Expand All @@ -79,8 +79,8 @@ macro_rules! float_variant {
}

pub fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(other_val) = other.extract::<Self>() {
return Ok(Self(self.0 * other_val.0));
if let Ok(other_val) = other.cast::<Self>() {
return Ok(Self(self.0 * other_val.get().0));
}
let other_val = other.extract::<$type>()?;
Ok(Self(self.0 * other_val))
Expand Down Expand Up @@ -196,6 +196,19 @@ macro_rules! float_variant {
op: CompareOp,
py: Python<'py>,
) -> PyResult<Borrowed<'py, 'py, PyAny>> {
// Fast path: same type (cast avoids a discarded PyErr and a __float__ hop)
if let Ok(other_val) = other.cast::<Self>() {
let other_float = other_val.get().0;
let result = match op {
CompareOp::Eq => self.0 == other_float,
CompareOp::Ne => self.0 != other_float,
CompareOp::Lt => self.0 < other_float,
CompareOp::Le => self.0 <= other_float,
CompareOp::Gt => self.0 > other_float,
CompareOp::Ge => self.0 >= other_float,
};
return Ok(PyBool::new(py, result).into_any());
}
// Try to convert other to our type first
if let Ok(other_float) = other.extract::<$type>() {
let result = match op {
Expand Down
12 changes: 12 additions & 0 deletions src/fable-library-py/src/ints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,18 @@ macro_rules! integer_variant {
/// Performs division with support for both integer and floating-point divisors.
/// Returns zero division error for zero divisors.
pub fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult<$name> {
// Fast path: same type — direct typed integer division. Avoids the
// __int__ hop of the OtherType extract, and the float-fallback
// precision loss for large/negative operands.
if let Ok(other_val) = other.cast::<$name>() {
let divisor = other_val.get().0;
if divisor == 0 {
return Err(PyErr::new::<exceptions::PyZeroDivisionError, _>(
"Cannot divide by zero",
));
}
return Ok($name(self.0 / divisor));
}
let value = other.extract::<OtherType>();
match value {
Ok(OtherType::Int(value)) => {
Expand Down
20 changes: 20 additions & 0 deletions src/fable-library-py/tests/test_ints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from array import array
from decimal import Decimal

import pytest

from fable_library.core import byte, int16, int32, int64, sbyte, uint16, uint32, uint64
from pydantic import BaseModel

Expand Down Expand Up @@ -179,6 +181,24 @@ def test_divide():
assert byte(10) / 3.0 == 3.3333333333333335


def test_divide_same_type():
# Same-type division uses the typed integer fast path (truncates toward
# zero, matching .NET) rather than a float fallback.
assert int32(100) / int32(7) == 14
assert int32(-100) / int32(7) == -14
assert int32(100) / int32(-7) == -14
assert int64(100) / int64(7) == 14
assert byte(10) / byte(3) == 3
# uint64 operand exceeding i64::MAX: the old OtherType path fell back to
# f64 division and lost precision; the typed path is exact.
assert uint64(18446744073709551615) / uint64(7) == 2635249153387078802


def test_divide_same_type_by_zero():
with pytest.raises(ZeroDivisionError):
int32(1) / int32(0)


def test_hash():
assert hash(byte(42)) != 0
assert hash(sbyte(42)) != 0
Expand Down
Loading