diff --git a/CHANGES.rst b/CHANGES.rst index be6a7341..9545eb06 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,9 @@ Bug fixes --------- -- Ensure headers words are never turned into signed 32-bit numpy integers. +- Ensure headers words are never turned into signed 32- or 64-bit numpy + integers. [#538, #540] + 4.3 (2025-03-10) ================ diff --git a/baseband/base/header.py b/baseband/base/header.py index 84a5711b..6204df9a 100644 --- a/baseband/base/header.py +++ b/baseband/base/header.py @@ -118,12 +118,18 @@ def setter(words, value): raise ValueError("no default value so cannot set to 'None'.") value = default elif value is True: + # Special case: may set multiple bits in invariant masks. value = bit_mask - elif np.any(value & bit_mask != value): - raise ValueError("{0} cannot be represented with {1} bits" - .format(value, bit_length)) + else: + if isinstance(value, (np.integer, np.bool_)): + # Turn into integer to avoid influencing promotion. + value = int(value) + if np.any(value & bit_mask != value): + raise ValueError("{0} cannot be represented with {1} bits" + .format(value, bit_length)) + if bit_length == 64: - word1 = value & (1 << 32) - 1 + word1 = value & 0xffffffff word2 = value >> 32 words[word_index] = word1 words[word_index + 1] = word2 diff --git a/baseband/mark4/header.py b/baseband/mark4/header.py index 7ef2afda..5bd5193a 100644 --- a/baseband/mark4/header.py +++ b/baseband/mark4/header.py @@ -218,7 +218,7 @@ def fraction(self, fraction): raise ValueError("{0} ms is not a multiple of 1.25 ms" .format(ms)) self['bcd_fraction'] = bcd_encode(np.floor(ms + 1e-6) - .astype(np.int32)) + .astype(np.uint32)) def get_time(self): """Convert BCD time code to Time object. diff --git a/baseband/mark4/tests/test_mark4.py b/baseband/mark4/tests/test_mark4.py index af46381c..f0437b7f 100644 --- a/baseband/mark4/tests/test_mark4.py +++ b/baseband/mark4/tests/test_mark4.py @@ -224,6 +224,14 @@ def test_header(self, tmpdir): assert header13.ntrack == 53 assert abs(header13.time - header.time) < 1. * u.ns + @pytest.mark.parametrize("value", [1, np.int32(1), np.int64(1)]) + def test_header_word_type(self, value): + # Regression test for numpy scalars influencing the type of the words. + header = Mark4TrackHeader(words=None) + header["converter_id"] = value + assert isinstance(header.words, list) + assert all(type(w) is int for w in header.words) + def test_invariant_pattern(self): with open(SAMPLE_FILE, 'rb') as fh: fh.seek(0xa88) diff --git a/docs/nitpick-exceptions b/docs/nitpick-exceptions index 9c4ba40c..966881ef 100644 --- a/docs/nitpick-exceptions +++ b/docs/nitpick-exceptions @@ -23,6 +23,7 @@ py:class a set-like object providing a view on D's items py:class a set-like object providing a view on D's keys py:class v, remove specified key and return the corresponding value. py:class None. Update D from dict/iterable E and F. +py:class None. Update D from mapping/iterable E and F. py:class an object providing a view on D's values py:class a shallow copy of D # Same but for OrderedDict (used in DADAHeader).