-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
BUG: typedef long double npy_longdouble;
creates problems on platforms with double == longdouble
#20348
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
Comments
I propose the following: #if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE
#if defined __MINGW32__ && __LDBL_DIG__ != __DBL_DIG__
typedef double npy_longdouble;
#else
typedef long double npy_longdouble;
#endif
#define NPY_LONGDOUBLE_FMT "g"
#else
typedef long double npy_longdouble;
#define NPY_LONGDOUBLE_FMT "Lg"
#endif this should work for:
I can't comment on the cygwin toolchain. This should be considered separately. |
It's not clear to me what problems it creates, but also having #if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE
npy_longdouble r = modf(x, ptr);
#else
npy_longdouble r = modfl(x, ptr);
#endif
// or just
npy_longdouble r = modfl(x, (long double*)ptr); also, another example with C++ might be duplicate template instantiations: template< typename T >
class X
{
};
template class X< double >;
#if NPY_SIZEOF_LONGDOUBLE != NPY_SIZEOF_DOUBLE
template class X< npy_longdouble >;
#endif EDIT: build warnings/errors should occur with undefined pointer casting only. |
With Cygwin I think there's nothing to worry about: people who use Cygwin should build everything (Python itself and all Python packages) with Cygwin's default compilers, and not use MSVC. The problems here all stem from mixing different compiler toolchains. |
@seiko2plus, why do you think you will get |
@carlkl, I meant explicitly reverting the changes back to |
Again: numpy compiled with MSVC is going to have |
@carlkl, Have you built NumPy after #19713 on |
@carlkl, this issue may related to |
I think @seiko2plus is correct about Why would we even need to map In a sane world we'd never need to do that indeed. If they're the same size, one could always happily use However, on Windows we're in this weird situation where we have different compilers that treat So, why do we care about that at all? Just don't mix compilers! Yes yes, you are right - one should not mix compilers, that's a bad idea. One should just build all packages one wants to use with a single compiler toolchain (and otherwise, a 100% compatible second toolchain). And this can be done on all platforms. Except, on Windows. Why, what's the problem on Windows? In summary: SciPy (and a few other projects, but mainly SciPy) needs a Fortran compiler. Python itself, NumPy and other projects containing C/C++/Cython code will be built with MSVC. So we need a MSVC-compatible Fortran compiler. There are about zero ideal candidates (Intel Fortran has licensing restrictions, gfortran from mingw-w64 defaults to 80-bit long double). For people who build SciPy for themselves, or for things like Anaconda defaults where there's no issue about paying for Intel Fortran and agreeing to the EULA fineprint, should all just use Intel Fortran. Cygwin can go all-Mingw. The troublesome cases are: building SciPy wheels, and building conda-forge packages. Maybe we can have an LLVM-based toolchain at some point: clang, clang++, plus one of the two Flangs or LFortran. But none of those work today. That all works today, right? Yes it does. It works because of this hacky thing we're doing in Can we have a special compiler toolchain just for SciPy which fixes the 80-bit long double thing from Mingw? Yes, this is what @carlkl can do. It will work to build wheels. However, it will not work well for conda-forge, because conda-forge ships compilers and then builds with those. And they already ship an unmodified mingw toolchain. Shipping also a custom one just for SciPy will not be well-received most likely (Cc @isuruf in case I'm wrong). Is there a way around that then? Well, maybe. Can we put in the patch @carlkl suggested above? Basically, we define If needed we can even put that patch in with an extra safety switch: |
I wonder whether we are running into this problem because the Numpy includes have two roles:
Obviously the For the second case, we do have a problem, because, in certain obvious cases that Ralf has laid out, the current compiler may disagree with with the original compiler about But - was the MSVC / mingw case the only case where that arises? Is the whole dance to get Would it even work, going in the opposite direction, compiling against GCC Numpy, using MSVC? |
My final conclusion: The widespread use of hardcoded |
I think it's fair to simply say "don't do that". It makes no sense, unless you have no other choice. And I can't think of any reasons right now other than needing a Fortran compiler. That we even think this is a normal use case in general is PyPI's fault I think - it has a broken binary distribution model, that was not designed with this kind of issue in mind. If you want to use GCC, then great - use it to build NumPy as well.
I don't. I did search for
Fair enough. One more thought though Carl. Given that for older releases we do have headers that will work, it'd be great to have them in 1.22.x as well - because whatever we have there we'll need for the next 2.5-3 years. What do you think of putting in your proposed patch anyway, behind an |
I would like to summarize the problems we as follows: What we want to use a standard mingw-w64 toolchain, no special version. For such a case we get extended precision long doubles. So defining Such a szenario would be entirely in the hands of the maintainer of the affected package and would have no side effects. So +1 from me. |
We coul generate such a variable during the header generation/config step, no? |
The idea was that the maintainer of a third-party package that depends on numpy can define |
ouch!, Thank you Ralf for that rich comment, it wasn't clear to me but now I understand what I did. I'm guilty but the implementation of the patch---
numpy/core/include/numpy/npy_common.h | 25 +++++++++---
.../core/src/npymath/npy_math_internal.h.src | 40 ++++++++++++-------
2 files changed, 46 insertions(+), 19 deletions(-)
diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h
index 57cc592b9..6eb121c6c 100644
--- a/numpy/core/include/numpy/npy_common.h
+++ b/numpy/core/include/numpy/npy_common.h
@@ -356,14 +356,29 @@ typedef unsigned long npy_ulonglong;
typedef unsigned char npy_bool;
#define NPY_FALSE 0
#define NPY_TRUE 1
-
-
+/*
+ * `NPY_SIZEOF_LONGDOUBLE` isn't usually equal to sizeof(long double),
+ * in some certain cases may be forced to be equal to sizeof(double)
+ * even against the compiler implementation.
+ *
+ * Therefore, avoid `long double`, use `npy_longdouble` instead,
+ * and when it comes to standard math functions make sure of using
+ * the double version when `NPY_SIZEOF_LONGDOUBLE` == `NPY_SIZEOF_DOUBLE`.
+ * For example:
+ * npy_longdouble *ptr, x;
+ * #if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE
+ * npy_longdouble r = modf(x, ptr);
+ * #else
+ * npy_longdouble r = modfl(x, ptr);
+ * #endif
+ */
#if NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE
- #define NPY_LONGDOUBLE_FMT "g"
+ #define NPY_LONGDOUBLE_FMT "g"
+ typedef double npy_longdouble;
#else
- #define NPY_LONGDOUBLE_FMT "Lg"
+ #define NPY_LONGDOUBLE_FMT "Lg"
+ typedef long double npy_longdouble;
#endif
-typedef long double npy_longdouble;
#ifndef Py_USING_UNICODE
#error Must use Python with unicode enabled.
diff --git a/numpy/core/src/npymath/npy_math_internal.h.src b/numpy/core/src/npymath/npy_math_internal.h.src
index dd2424db8..5b418342f 100644
--- a/numpy/core/src/npymath/npy_math_internal.h.src
+++ b/numpy/core/src/npymath/npy_math_internal.h.src
@@ -477,10 +477,16 @@ NPY_INPLACE @type@ npy_frexp@c@(@type@ x, int* exp)
/**begin repeat
* #type = npy_longdouble, npy_double, npy_float#
+ * #TYPE = LONGDOUBLE, DOUBLE, FLOAT#
* #c = l,,f#
* #C = L,,F#
*/
-
+#undef NPY__FP_SFX
+#if NPY_SIZEOF_@TYPE@ == NPY_SIZEOF_DOUBLE
+ #define NPY__FP_SFX(X) X
+#else
+ #define NPY__FP_SFX(X) NPY_CAT(X, @c@)
+#endif
/*
* On arm64 macOS, there's a bug with sin, cos, and tan where they don't
* raise "invalid" when given INFINITY as input.
@@ -506,7 +512,7 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x)
return (x - x);
}
#endif
- return @kind@@c@(x);
+ return NPY__FP_SFX(@kind@)(x);
}
#endif
@@ -521,7 +527,7 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x)
#ifdef HAVE_@KIND@@C@
NPY_INPLACE @type@ npy_@kind@@c@(@type@ x, @type@ y)
{
- return @kind@@c@(x, y);
+ return NPY__FP_SFX(@kind@)(x, y);
}
#endif
/**end repeat1**/
@@ -529,21 +535,21 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x, @type@ y)
#ifdef HAVE_MODF@C@
NPY_INPLACE @type@ npy_modf@c@(@type@ x, @type@ *iptr)
{
- return modf@c@(x, iptr);
+ return NPY__FP_SFX(modf)(x, iptr);
}
#endif
#ifdef HAVE_LDEXP@C@
NPY_INPLACE @type@ npy_ldexp@c@(@type@ x, int exp)
{
- return ldexp@c@(x, exp);
+ return NPY__FP_SFX(ldexp)(x, exp);
}
#endif
#ifdef HAVE_FREXP@C@
NPY_INPLACE @type@ npy_frexp@c@(@type@ x, int* exp)
{
- return frexp@c@(x, exp);
+ return NPY__FP_SFX(frexp)(x, exp);
}
#endif
@@ -566,10 +572,10 @@ NPY_INPLACE @type@ npy_cbrt@c@(@type@ x)
#else
NPY_INPLACE @type@ npy_cbrt@c@(@type@ x)
{
- return cbrt@c@(x);
+ return NPY__FP_SFX(cbrt)(x);
}
#endif
-
+#undef NPY__FP_SFX
/**end repeat**/
@@ -579,10 +585,16 @@ NPY_INPLACE @type@ npy_cbrt@c@(@type@ x)
/**begin repeat
* #type = npy_float, npy_double, npy_longdouble#
+ * #TYPE = FLOAT, DOUBLE, LONGDOUBLE#
* #c = f, ,l#
* #C = F, ,L#
*/
-
+#undef NPY__FP_SFX
+#if NPY_SIZEOF_@TYPE@ == NPY_SIZEOF_DOUBLE
+ #define NPY__FP_SFX(X) X
+#else
+ #define NPY__FP_SFX(X) NPY_CAT(X, @c@)
+#endif
@type@ npy_heaviside@c@(@type@ x, @type@ h0)
{
if (npy_isnan(x)) {
@@ -599,10 +611,10 @@ NPY_INPLACE @type@ npy_cbrt@c@(@type@ x)
}
}
-#define LOGE2 NPY_LOGE2@c@
-#define LOG2E NPY_LOG2E@c@
-#define RAD2DEG (180.0@c@/NPY_PI@c@)
-#define DEG2RAD (NPY_PI@c@/180.0@c@)
+#define LOGE2 NPY__FP_SFX(NPY_LOGE2)
+#define LOG2E NPY__FP_SFX(NPY_LOG2E)
+#define RAD2DEG (NPY__FP_SFX(180.0)/NPY__FP_SFX(NPY_PI))
+#define DEG2RAD (NPY__FP_SFX(NPY_PI)/NPY__FP_SFX(180.0))
NPY_INPLACE @type@ npy_rad2deg@c@(@type@ x)
{
@@ -756,7 +768,7 @@ npy_divmod@c@(@type@ a, @type@ b, @type@ *modulus)
#undef LOG2E
#undef RAD2DEG
#undef DEG2RAD
-
+#undef NPY__FP_SFX
/**end repeat**/
/**begin repeat
-- It simply avoids using However, it's a temporary solution just to make it like before but IMHO the righties way is to use |
Thanks for this patch Sayed! It makes sense to avoid using Lines 114 to 118 in 8dbd507
https://github.com/numpy/numpy/blob/main/numpy/core/setup_common.py#L292-295 numpy/numpy/core/src/npymath/npy_math_complex.c.src Lines 1492 to 1496 in 2513620
numpy/numpy/core/include/numpy/npy_common.h Lines 314 to 317 in 2513620
Looking at compiler warnings on macOS, there's only a few related to mixing |
That's an interesting idea. Hard to tell if that covers all bases, but seems like a reasonable thing to do either way. |
I have repeated the scipy build with mingw-w64 and test with a patched
For this case
My conclusion is still: |
I would really like to the patch #20348 (comment) from @seiko2plus as soon as possible. But we would need an numpy wheel for that. |
Well, good timing - let's get it in by this weekend, and it'll appear in |
@seiko2plus do you have time to submit your patch as a PR? |
@rgommers, sure, right away |
maybe because of
I agree with you, unavoidable at the current moment but as I mentioned before, it would be better to handle it from a python level to reduce the efforts in maintaining |
long double is a lost cause IMHO. It is an artifact from a bygone era that has not yet disappeared for compatibility reasons. It is not supported on new hardware like ARM, RISC-V. |
ARM64 supports quad precision. But I agree about extended precision, it didn't offer enough precision to really make a difference. I'd like to have quad precision though, if only to simplify the time problem. |
Adding a quad precision dtype and then deprecating everything long double sounds appealing. Just doing the deprecation part of that still sounds appealing. Good roadmap item? |
Generally sounds good - but I think we'd need a NEP - no? Because we've also got to sort out the naming mess - Float128 etc ... much discussed previously - e.g. #10288 |
Also - cross ref to IEEE 128-bit float proposal : #7647 |
and: #14574 |
Hmm, that's basically the same discussion in three places (four if you count this issue, and there may be more elsewhere). It looks like we should close all except one, and rename that issue so it's 100% clear that's the one tracking issue for a 128-bit quad precision dtype + dealing with the current |
The advantage of using quad precision is that we get quad precision Blas and Lapack mlapack for free. |
Nothing BLAS/LAPACK related is for free unfortunately. We'd require users to build that from source I think to get access to |
It will be a lot of work, especially for systems without hardeware support. GCC includes libquadmath, but unfortunately with LGPL license. Windows doesn't support data types with quad precision. I already suggested Sleef as an option here: #14574 (comment). |
Can I revisit this one? @carlkl pointed out that this is a rather significant change in behavior. Previously, for platforms and compilers where
double == long double
, at Numpy compile time, such as MSVC, this would givenpy_longdouble
asdouble
. This is useful in the case when we are compiling using Numpy, but with a compiler where it is not true thatdouble == long double
. One example is compiling Scipy with mingw-w64, where, by defaultlong double
is Float80. Could y'all say more about why this change is needed? Why do we need to forcenpy_longdouble
to be the compiler long double, rather than Numpylong double
?Originally posted by @matthew-brett in #20206 (comment)
Also pinging @seiko2plus, @mattip, and since it seems faintly C++ related @serge-sans-paille
The text was updated successfully, but these errors were encountered: