diff --git a/doc/CHANGELOG.rst b/doc/CHANGELOG.rst index 5848e7d66..217dd8320 100644 --- a/doc/CHANGELOG.rst +++ b/doc/CHANGELOG.rst @@ -19,6 +19,11 @@ Latest Changes: For editable installs, ``python setup.py --enable-tracing develop`` must now be done with ``python setup.py develop --enable-tracing``. + - Update for tests for numpy 2.0. + + - Support of np.float16 conversion with arrays. + + - **1.5.0 - 2023-04-03** - Support for Python 3.12 diff --git a/native/common/jp_convert.cpp b/native/common/jp_convert.cpp index 919417461..5d288e044 100644 --- a/native/common/jp_convert.cpp +++ b/native/common/jp_convert.cpp @@ -14,10 +14,61 @@ See NOTICE file for details. *****************************************************************************/ #include "jpype.h" +#include +#include namespace { +template +class Half +{ +public: + static jvalue convert(void* c) + { + uint16_t i = *(uint16_t*) c; + uint32_t sign = (i&0x8000)>>15; + uint32_t man = (i&0x7C00)>>10; + uint32_t frac = (i&0x03ff); + uint32_t k = sign<<31; + + if (man == 0) + { + // subnormal numbers + if (frac != 0) + { + frac = frac | (frac >> 1); + frac = frac | (frac >> 2); + frac = frac | (frac >> 4); + frac = frac | (frac >> 8); + int zeros = std::bitset<32>(~frac).count(); + man = 127-zeros+7; + man <<= 23; + frac <<= zeros-8; + frac &= 0x7fffff; + k |= man | frac; + } + } + else if (man < 31) + { + // normal numbers + man = man-15+127; + man <<= 23; + frac <<= 13; + k |= man | frac; + } + else + { + // to infinity and beyond! + if (frac == 0) + k |= 0x7f800000; + else + k |= 0x7f800001 | ((frac&0x200)<<12); + } + return func(&k); + } +}; + template class Convert { @@ -385,6 +436,31 @@ jconverter getConverter(const char* from, int itemsize, const char* to) case 'd': return &Convert::toD; } break; + case 'e': + if (reverse) switch (to[0]) + { + case 'z': return &Reverse::toZ>::convert>::call4; + case 'b': return &Reverse::toB>::convert>::call4; + case 'c': return &Reverse::toC>::convert>::call4; + case 's': return &Reverse::toS>::convert>::call4; + case 'i': return &Reverse::toI>::convert>::call4; + case 'j': return &Reverse::toJ>::convert>::call4; + case 'f': return &Reverse::toF>::convert>::call4; + case 'd': return &Reverse::toD>::convert>::call4; + } + else switch (to[0]) + { + case 'z': return &Half::toZ>::convert; + case 'b': return &Half::toB>::convert; + case 'c': return &Half::toC>::convert; + case 's': return &Half::toS>::convert; + case 'i': return &Half::toI>::convert; + case 'j': return &Half::toJ>::convert; + case 'f': return &Half::toF>::convert; + case 'd': return &Half::toD>::convert; + } + break; + case 'n': if (reverse) switch (to[0]) { diff --git a/test/jpypetest/test_conversionInt.py b/test/jpypetest/test_conversionInt.py index bb94ca025..8c9721c51 100644 --- a/test/jpypetest/test_conversionInt.py +++ b/test/jpypetest/test_conversionInt.py @@ -73,10 +73,10 @@ def testIntFromFloat(self): self.Test.callInt(float(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") - def testIntFromNPFloat(self): + def testIntFromNPFloat16(self): import numpy as np with self.assertRaises(TypeError): - self.Test.callInt(np.float_(2)) + self.Test.callInt(np.float16(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") def testIntFromNPFloat32(self): diff --git a/test/jpypetest/test_conversionLong.py b/test/jpypetest/test_conversionLong.py index ae3f31313..256b4ed0c 100644 --- a/test/jpypetest/test_conversionLong.py +++ b/test/jpypetest/test_conversionLong.py @@ -73,10 +73,10 @@ def testLongFromFloat(self): self.Test.callLong(float(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") - def testLongFromNPFloat(self): + def testLongFromNPFloat16(self): import numpy as np with self.assertRaises(TypeError): - self.Test.callLong(np.float_(2)) + self.Test.callLong(np.float16(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") def testLongFromNPFloat32(self): diff --git a/test/jpypetest/test_conversionShort.py b/test/jpypetest/test_conversionShort.py index d3fd81ffa..0c3d072c8 100644 --- a/test/jpypetest/test_conversionShort.py +++ b/test/jpypetest/test_conversionShort.py @@ -73,10 +73,10 @@ def testShortFromFloat(self): self.Test.callShort(float(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") - def testShortFromNPFloat(self): + def testShortFromNPFloat16(self): import numpy as np with self.assertRaises(TypeError): - self.Test.callShort(np.float_(2)) + self.Test.callShort(np.float16(2)) @common.unittest.skipUnless(haveNumpy(), "numpy not available") def testShortFromNPFloat32(self): diff --git a/test/jpypetest/test_jboolean.py b/test/jpypetest/test_jboolean.py index ed0f6bd99..b27c63fd0 100644 --- a/test/jpypetest/test_jboolean.py +++ b/test/jpypetest/test_jboolean.py @@ -100,10 +100,10 @@ def testBooleanFromFloat(self): self.Test.callBoolean(float(2)) @common.requireNumpy - def testBooleanFromNPFloat(self): + def testBooleanFromNPFloat16(self): import numpy as np with self.assertRaises(TypeError): - self.Test.callBoolean(np.float_(2)) + self.Test.callBoolean(np.float16(2)) @common.requireNumpy def testBooleanFromNPFloat32(self): diff --git a/test/jpypetest/test_jbyte.py b/test/jpypetest/test_jbyte.py index 55bfd4ec8..a4b995c9b 100644 --- a/test/jpypetest/test_jbyte.py +++ b/test/jpypetest/test_jbyte.py @@ -108,10 +108,10 @@ def testByteFromFloat(self): self.fixture.callByte(float(2)) @common.requireNumpy - def testByteFromNPFloat(self): + def testByteFromNPFloat16(self): import numpy as np with self.assertRaises(TypeError): - self.fixture.callByte(np.float_(2)) + self.fixture.callByte(np.float16(2)) @common.requireNumpy def testByteFromNPFloat32(self): diff --git a/test/jpypetest/test_jdouble.py b/test/jpypetest/test_jdouble.py index 09c03351b..cf3b89799 100644 --- a/test/jpypetest/test_jdouble.py +++ b/test/jpypetest/test_jdouble.py @@ -374,8 +374,8 @@ def testArraySetFromNPDouble(self): self.assertElementsAlmostEqual(a, jarr) @common.requireNumpy - def testArrayInitFromNPFloat(self): - a = np.random.random(100).astype(np.float_) + def testArrayInitFromNPFloat16(self): + a = np.random.random(100).astype(np.float16) jarr = JArray(JDouble)(a) self.assertElementsAlmostEqual(a, jarr) @@ -436,3 +436,15 @@ def __len__(self): def testCastBoolean(self): self.assertEqual(JDouble._canConvertToJava(JBoolean(True)), "none") + + @common.requireNumpy + def testNPFloat16(self): + v= [0.000000e+00, 5.960464e-08, 1.788139e-07, 1.788139e-07, 4.172325e-07, 8.940697e-07, 1.847744e-06, 3.755093e-06, 7.569790e-06, 1.519918e-05, 3.045797e-05, 6.097555e-05, 6.103516e-05, 3.332520e-01, 1.000000e+00, 6.550400e+04, np.inf, -np.inf] + a = np.array(v, dtype=np.float16) + jarr = JArray(JDouble)(a) + for v1,v2 in zip(a, jarr): + self.assertEqual(v1,v2) + a = np.array([np.nan], dtype=np.float16) + jarr = JArray(JDouble)(a) + self.assertTrue(np.isnan(jarr[0])) + diff --git a/test/jpypetest/test_jfloat.py b/test/jpypetest/test_jfloat.py index 4fbce3591..eb63fe168 100644 --- a/test/jpypetest/test_jfloat.py +++ b/test/jpypetest/test_jfloat.py @@ -382,8 +382,8 @@ def testArraySetFromNPDouble(self): self.assertElementsAlmostEqual(a, jarr) @common.requireNumpy - def testArrayInitFromNPFloat(self): - a = np.random.random(100).astype(np.float_) + def testArrayInitFromNPFloat16(self): + a = np.random.random(100).astype(np.float16) jarr = JArray(JFloat)(a) self.assertElementsAlmostEqual(a, jarr) @@ -441,3 +441,15 @@ def __len__(self): ja[:] = [1, 2, 3] with self.assertRaisesRegex(ValueError, "mismatch"): ja[:] = a + + @common.requireNumpy + def testNPFloat16(self): + v= [0.000000e+00, 5.960464e-08, 1.788139e-07, 1.788139e-07, 4.172325e-07, 8.940697e-07, 1.847744e-06, 3.755093e-06, 7.569790e-06, 1.519918e-05, 3.045797e-05, 6.097555e-05, 6.103516e-05, 3.332520e-01, 1.000000e+00, 6.550400e+04, np.inf, -np.inf] + a = np.array(v, dtype=np.float16) + jarr = JArray(JFloat)(a) + for v1,v2 in zip(a, jarr): + self.assertEqual(v1,v2) + a = np.array([np.nan], dtype=np.float16) + jarr = JArray(JFloat)(a) + self.assertTrue(np.isnan(jarr[0])) +