Skip to content

Commit db6b84e

Browse files
authored
Merge pull request #175 from SwayamInSync/floor_divide
2 parents 87cc9ba + d87594b commit db6b84e

File tree

4 files changed

+174
-1
lines changed

4 files changed

+174
-1
lines changed

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,39 @@ quad_div(const Sleef_quad *a, const Sleef_quad *b)
467467
return Sleef_divq1_u05(*a, *b);
468468
}
469469

470+
static inline Sleef_quad
471+
quad_floor_divide(const Sleef_quad *a, const Sleef_quad *b)
472+
{
473+
// Handle NaN inputs
474+
if (Sleef_iunordq1(*a, *b)) {
475+
return Sleef_iunordq1(*a, *a) ? *a : *b;
476+
}
477+
478+
// inf / finite_nonzero or -inf / finite_nonzero -> NaN
479+
// But inf / 0 -> inf
480+
if (quad_isinf(a) && quad_isfinite(b) && !Sleef_icmpeqq1(*b, QUAD_ZERO)) {
481+
return QUAD_NAN;
482+
}
483+
484+
// 0 / 0 (including -0.0 / 0.0, 0.0 / -0.0, -0.0 / -0.0) -> NaN
485+
if (Sleef_icmpeqq1(*a, QUAD_ZERO) && Sleef_icmpeqq1(*b, QUAD_ZERO)) {
486+
return QUAD_NAN;
487+
}
488+
489+
Sleef_quad quotient = Sleef_divq1_u05(*a, *b);
490+
Sleef_quad result = Sleef_floorq1(quotient);
491+
492+
// floor_divide semantics: when result is -0.0 from non-zero numerator, convert to -1.0
493+
// This happens when: (negative & non-zero)/+inf, (positive & non-zero)/-inf
494+
// But NOT when numerator is ±0.0 (then result stays as ±0.0)
495+
if (Sleef_icmpeqq1(result, QUAD_ZERO) && quad_signbit(&result) &&
496+
!Sleef_icmpeqq1(*a, QUAD_ZERO)) {
497+
return Sleef_negq1(QUAD_ONE); // -1.0
498+
}
499+
500+
return result;
501+
}
502+
470503
static inline Sleef_quad
471504
quad_pow(const Sleef_quad *a, const Sleef_quad *b)
472505
{
@@ -696,6 +729,38 @@ ld_div(const long double *a, const long double *b)
696729
return (*a) / (*b);
697730
}
698731

732+
static inline long double
733+
ld_floor_divide(const long double *a, const long double *b)
734+
{
735+
// Handle NaN inputs
736+
if (isnan(*a) || isnan(*b)) {
737+
return isnan(*a) ? *a : *b;
738+
}
739+
740+
// inf / finite_nonzero or -inf / finite_nonzero -> NaN
741+
// But inf / 0 -> inf
742+
if (isinf(*a) && isfinite(*b) && *b != 0.0L) {
743+
return NAN;
744+
}
745+
746+
// 0 / 0 (including -0.0 / 0.0, 0.0 / -0.0, -0.0 / -0.0) -> NaN
747+
if (*a == 0.0L && *b == 0.0L) {
748+
return NAN;
749+
}
750+
751+
// Compute a / b and apply floor
752+
long double result = floorl((*a) / (*b));
753+
754+
// floor_divide semantics: when result is -0.0 from non-zero numerator, convert to -1.0
755+
// This happens when: (negative & non-zero)/+inf, (positive & non-zero)/-inf
756+
// But NOT when numerator is ±0.0 (then result stays as ±0.0)
757+
if (result == 0.0L && signbit(result) && *a != 0.0L) {
758+
return -1.0L;
759+
}
760+
761+
return result;
762+
}
763+
699764
static inline long double
700765
ld_pow(const long double *a, const long double *b)
701766
{

quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ init_quad_binary_ops(PyObject *numpy)
218218
}
219219
// Note: true_divide is an alias to divide in NumPy for floating-point types
220220
// No need to register separately
221+
if (create_quad_binary_ufunc<quad_floor_divide, ld_floor_divide>(numpy, "floor_divide") < 0) {
222+
return -1;
223+
}
221224
if (create_quad_binary_ufunc<quad_pow, ld_pow>(numpy, "power") < 0) {
222225
return -1;
223226
}

quaddtype/release_tracker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| logaddexp |||
1414
| logaddexp2 |||
1515
| true_divide |||
16-
| floor_divide | | |
16+
| floor_divide | | |
1717
| negative |||
1818
| positive |||
1919
| power |||

quaddtype/tests/test_quaddtype.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,111 @@ def test_true_divide_special_properties():
732732
np.testing.assert_allclose(float(result), 3.0, rtol=1e-30)
733733

734734

735+
@pytest.mark.parametrize(
736+
"x_val",
737+
[
738+
0.0, 1.0, 2.0, -1.0, -2.0,
739+
0.5, -0.5,
740+
100.0, 1000.0, -100.0, -1000.0,
741+
1e-10, -1e-10, 1e-20, -1e-20,
742+
float("inf"), float("-inf"), float("nan"), float("-nan"), -0.0
743+
]
744+
)
745+
@pytest.mark.parametrize(
746+
"y_val",
747+
[
748+
0.0, 1.0, 2.0, -1.0, -2.0,
749+
0.5, -0.5,
750+
100.0, 1000.0, -100.0, -1000.0,
751+
1e-10, -1e-10, 1e-20, -1e-20,
752+
float("inf"), float("-inf"), float("nan"), float("-nan"), -0.0
753+
]
754+
)
755+
def test_floor_divide(x_val, y_val):
756+
"""Test floor_divide ufunc with comprehensive edge cases"""
757+
x_quad = QuadPrecision(str(x_val))
758+
y_quad = QuadPrecision(str(y_val))
759+
760+
# Compute using QuadPrecision
761+
result_quad = np.floor_divide(x_quad, y_quad)
762+
763+
# Compute using float64 for comparison
764+
result_float64 = np.floor_divide(np.float64(x_val), np.float64(y_val))
765+
766+
# Compare results
767+
if np.isnan(result_float64):
768+
assert np.isnan(float(result_quad)), f"Expected NaN for floor_divide({x_val}, {y_val})"
769+
elif np.isinf(result_float64):
770+
assert np.isinf(float(result_quad)), f"Expected inf for floor_divide({x_val}, {y_val})"
771+
assert np.sign(float(result_quad)) == np.sign(result_float64), f"Sign mismatch for floor_divide({x_val}, {y_val})"
772+
else:
773+
# For finite results, check relative tolerance
774+
# Use absolute tolerance for large numbers due to float64 precision limits
775+
atol = max(1e-10, abs(result_float64) * 1e-9) if abs(result_float64) > 1e6 else 1e-10
776+
np.testing.assert_allclose(
777+
float(result_quad), result_float64, rtol=1e-12, atol=atol,
778+
err_msg=f"Mismatch for floor_divide({x_val}, {y_val})"
779+
)
780+
def test_floor_divide_special_properties():
781+
"""Test special mathematical properties of floor_divide"""
782+
# floor_divide(x, 1) = floor(x)
783+
x = QuadPrecision("42.7")
784+
result = np.floor_divide(x, QuadPrecision("1.0"))
785+
np.testing.assert_allclose(float(result), 42.0, rtol=1e-30)
786+
787+
# floor_divide(0, non-zero) = 0
788+
result = np.floor_divide(QuadPrecision("0.0"), QuadPrecision("5.0"))
789+
assert float(result) == 0.0
790+
791+
# floor_divide by 0 gives inf (with appropriate sign)
792+
result = np.floor_divide(QuadPrecision("1.0"), QuadPrecision("0.0"))
793+
assert np.isinf(float(result)) and float(result) > 0
794+
795+
result = np.floor_divide(QuadPrecision("-1.0"), QuadPrecision("0.0"))
796+
assert np.isinf(float(result)) and float(result) < 0
797+
798+
# 0 / 0 = NaN
799+
result = np.floor_divide(QuadPrecision("0.0"), QuadPrecision("0.0"))
800+
assert np.isnan(float(result))
801+
802+
# inf / inf = NaN
803+
result = np.floor_divide(QuadPrecision("inf"), QuadPrecision("inf"))
804+
assert np.isnan(float(result))
805+
806+
# inf / finite_nonzero = NaN (NumPy behavior)
807+
result = np.floor_divide(QuadPrecision("inf"), QuadPrecision("100.0"))
808+
assert np.isnan(float(result))
809+
810+
# finite / inf = 0
811+
result = np.floor_divide(QuadPrecision("100.0"), QuadPrecision("inf"))
812+
assert float(result) == 0.0
813+
814+
# floor_divide rounds toward negative infinity
815+
result = np.floor_divide(QuadPrecision("7.0"), QuadPrecision("3.0"))
816+
assert float(result) == 2.0 # floor(7/3) = floor(2.333...) = 2
817+
818+
result = np.floor_divide(QuadPrecision("-7.0"), QuadPrecision("3.0"))
819+
assert float(result) == -3.0 # floor(-7/3) = floor(-2.333...) = -3
820+
821+
result = np.floor_divide(QuadPrecision("7.0"), QuadPrecision("-3.0"))
822+
assert float(result) == -3.0 # floor(7/-3) = floor(-2.333...) = -3
823+
824+
result = np.floor_divide(QuadPrecision("-7.0"), QuadPrecision("-3.0"))
825+
assert float(result) == 2.0 # floor(-7/-3) = floor(2.333...) = 2
826+
827+
# floor_divide(x, x) = 1 for positive finite non-zero x
828+
x = QuadPrecision("7.123456789")
829+
result = np.floor_divide(x, x)
830+
np.testing.assert_allclose(float(result), 1.0, rtol=1e-30)
831+
832+
# Relationship with floor and true_divide
833+
x = QuadPrecision("10.5")
834+
y = QuadPrecision("3.2")
835+
result_floor_divide = np.floor_divide(x, y)
836+
result_floor_true_divide = np.floor(np.true_divide(x, y))
837+
np.testing.assert_allclose(float(result_floor_divide), float(result_floor_true_divide), rtol=1e-30)
838+
839+
735840
def test_inf():
736841
assert QuadPrecision("inf") > QuadPrecision("1e1000")
737842
assert np.signbit(QuadPrecision("inf")) == 0

0 commit comments

Comments
 (0)