diff --git a/.github/workflows/big_endian.yml b/.github/workflows/big_endian.yml index 4def7c0..528ad17 100644 --- a/.github/workflows/big_endian.yml +++ b/.github/workflows/big_endian.yml @@ -137,7 +137,7 @@ jobs: python -m pip install --break-system-packages --no-deps . -v --no-build-isolation --force-reinstall && # Install test dependencies separately - python -m pip install --break-system-packages pytest pytest-run-parallel pytest-timeout mpmath && + python -m pip install --break-system-packages .[test] && python -m pytest -vvv --color=yes --timeout=600 --tb=short tests/ '" diff --git a/.github/workflows/test_old_cpu.yml b/.github/workflows/test_old_cpu.yml index ca75cc4..476a876 100644 --- a/.github/workflows/test_old_cpu.yml +++ b/.github/workflows/test_old_cpu.yml @@ -54,9 +54,9 @@ jobs: # For Sandy Bridge (x86-64-v2), we need to disable FMA code paths # since FMA instructions are not available on that microarchitecture if [ "${{ matrix.cpu[0] }}" = "snb" ]; then - pip install . --no-build-isolation -v -Csetup-args=-Ddisable_fma=true + pip install .[test] --no-build-isolation -v -Csetup-args=-Ddisable_fma=true else - pip install . --no-build-isolation -v + pip install .[test] --no-build-isolation -v fi - name: Test import on ${{ matrix.cpu[1] }} @@ -74,5 +74,4 @@ jobs: - name: Run tests on ${{ matrix.cpu[1] }} run: | - pip install pytest mpmath - sde -${{ matrix.cpu[0] }} -- python -m pytest tests/ -v --tb=short -v -s \ No newline at end of file + sde -${{ matrix.cpu[0] }} -- python -m pytest tests/ -v --tb=short -v -s diff --git a/pyproject.toml b/pyproject.toml index e7cacba..697216e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,8 @@ dependencies = [ test = [ "pytest", "mpmath", - "pytest-run-parallel" + "pytest-run-parallel", + "pandas" ] docs = [ diff --git a/src/csrc/scalar.c b/src/csrc/scalar.c index 4d23333..140e768 100644 --- a/src/csrc/scalar.c +++ b/src/csrc/scalar.c @@ -281,11 +281,11 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend) static PyObject * QuadPrecision_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs) { - PyObject *value; + PyObject *value = NULL; const char *backend_str = "sleef"; static char *kwlist[] = {"value", "backend", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|s", kwlist, &value, &backend_str)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Os", kwlist, &value, &backend_str)) { return NULL; } @@ -298,6 +298,9 @@ QuadPrecision_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs) return NULL; } + if (value == NULL) { + return (PyObject *)QuadPrecision_raw_new(backend); + } return (PyObject *)QuadPrecision_from_object(value, backend); } @@ -747,4 +750,4 @@ init_quadprecision_scalar(void) { QuadPrecision_Type.tp_base = &PyFloatingArrType_Type; return PyType_Ready(&QuadPrecision_Type); -} \ No newline at end of file +} diff --git a/tests/test_quaddtype.py b/tests/test_quaddtype.py index 0850e4b..52e4237 100644 --- a/tests/test_quaddtype.py +++ b/tests/test_quaddtype.py @@ -5996,4 +5996,50 @@ def test_sleef_purecfma_symbols(): for sym in purecfma_symbols[:5]: print(f" {sym}") if len(purecfma_symbols) > 5: - print(f" ... and {len(purecfma_symbols) - 5} more") \ No newline at end of file + print(f" ... and {len(purecfma_symbols) - 5} more") + + +def test_empty_construct(): + # Default backend (sleef): no-arg construction yields zero. + assert QuadPrecision() == QuadPrecision(0) == 0 + assert QuadPrecision().dtype == QuadPrecDType() + assert str(QuadPrecision()) == str(QuadPrecision(0)) + + # longdouble backend: no-arg construction also yields zero. + assert QuadPrecision(backend="longdouble") == QuadPrecision(0, backend="longdouble") == 0 + assert QuadPrecision(backend="longdouble").dtype == QuadPrecDType(backend="longdouble") + + # Round-trip through dtype.type(), this is the call path pandas uses + # in `_take_preprocess_indexer_and_fill_value` (see issue #83). + for backend in ("sleef", "longdouble"): + dtype = QuadPrecDType(backend=backend) + zero = dtype.type() + assert isinstance(zero, QuadPrecision) + assert zero == 0 + + # Invalid backend must still raise even when no value is supplied. + with pytest.raises(ValueError): + QuadPrecision(backend="bogus") + + # Explicit None should NOT be silently treated as zero, it must go + # through QuadPrecision_from_object and raise. + with pytest.raises((TypeError, ValueError)): + QuadPrecision(None) + + +def test_pandas_strrep(): + """Test that we can construct a pandas data frame with quad precision columns + + Make sure the string representation can be generated + """ + import pandas as pd + + BIG_NUMBER=123456789098765432123456789 + x = np.arange(500, dtype=np.float64) * BIG_NUMBER + y = np.arange(500, dtype=QuadPrecDType()) * BIG_NUMBER + df = pd.DataFrame({"col1": x, "col2": y}) + assert isinstance(str(df), str) # Make sure this doesn't fail + assert df["col1"].dtype == np.float64 + assert df["col2"].dtype == QuadPrecDType() + assert df["col1"].iloc[499] != QuadPrecision(499 * BIG_NUMBER) + assert df["col2"].iloc[499] == QuadPrecision(499 * BIG_NUMBER)