Repository: astropy/astropy

Issue:
Unsigned ints are not preserved when reading a row-less FITS table
### Description

When reading a FITS bintable with no rows, columns specified as unsigned int (by setting the **TZEROn** keyword) are incorrectly defined with a signed data type. 

### Expected behavior

Reading FITS files should preserve the data type. We need this because we sometimes use empty FITS files as templates, which are later filled.

### How to Reproduce

```python
>>> from astropy.io.fits import BinTableHDU
>>> from astropy.table import Table, Column
>>> tbl = Table([Column(name="unsigned16", dtype="uint16"), Column(name="unsigned32", dtype="uint32"), Column(name="unsigned64", dtype="uint64")])
>>> tbl
<Table length=0>
unsigned16 unsigned32 unsigned64
  uint16     uint32     uint64  
---------- ---------- ----------
>>> hdu = BinTableHDU(tbl)
>>> hdu.header
XTENSION= 'BINTABLE'           / binary table extension                         
BITPIX  =                    8 / array data type                                
NAXIS   =                    2 / number of array dimensions                     
NAXIS1  =                   14 / length of dimension 1                          
NAXIS2  =                    0 / length of dimension 2                          
PCOUNT  =                    0 / number of group parameters                     
GCOUNT  =                    1 / number of groups                               
TFIELDS =                    3 / number of table fields                         
TTYPE1  = 'unsigned16'                                                          
TFORM1  = 'I       '                                                            
TZERO1  =                32768                                                  
TTYPE2  = 'unsigned32'                                                          
TFORM2  = 'J       '                                                            
TZERO2  =           2147483648                                                  
TTYPE3  = 'unsigned64'                                                          
TFORM3  = 'K       '                                                            
TZERO3  =  9223372036854775808                                                  
>>> Table.read(hdu)
<Table length=0>
unsigned16 unsigned32 unsigned64
  int16      int32      int64   
---------- ---------- ----------
```
As reference, the same table with a single row:
```python
>>> tbl = Table([Column(name="unsigned16", dtype="uint16", data=[0]), Column(name="unsigned32", dtype="uint32", data=[0]), Column(name="unsigned64", dtype="uint64", data=[0])])
>>> tbl
<Table length=1>
unsigned16 unsigned32 unsigned64
  uint16     uint32     uint64  
---------- ---------- ----------
         0          0          0
>>> hdu = BinTableHDU(tbl)
>>> hdu.header
XTENSION= 'BINTABLE'           / binary table extension                         
BITPIX  =                    8 / array data type                                
NAXIS   =                    2 / number of array dimensions                     
NAXIS1  =                   14 / length of dimension 1                          
NAXIS2  =                    1 / length of dimension 2                          
PCOUNT  =                    0 / number of group parameters                     
GCOUNT  =                    1 / number of groups                               
TFIELDS =                    3 / number of table fields                         
TTYPE1  = 'unsigned16'                                                          
TFORM1  = 'I       '                                                            
TZERO1  =                32768                                                  
TTYPE2  = 'unsigned32'                                                          
TFORM2  = 'J       '                                                            
TZERO2  =           2147483648                                                  
TTYPE3  = 'unsigned64'                                                          
TFORM3  = 'K       '                                                            
TZERO3  =  9223372036854775808                                                  
>>> Table.read(hdu)
<Table length=1>
unsigned16 unsigned32 unsigned64
  uint16     uint32     uint64  
---------- ---------- ----------
         0          0          0
```


### Versions

This is astropy 6.0.1, numpy 1.26.4.

Discussion:

Patch:
diff --git a/astropy/io/fits/fitsrec.py b/astropy/io/fits/fitsrec.py
index 6289888515..ae364f9414 100644
--- a/astropy/io/fits/fitsrec.py
+++ b/astropy/io/fits/fitsrec.py
@@ -368,21 +368,16 @@ class FITS_rec(np.recarray):
             arr = column.array
 
             if arr is None:
-                array_size = 0
-            else:
-                array_size = len(arr)
+                # The input column had an empty array, so just use the fill
+                # value
+                continue
 
-            n = min(array_size, nrows)
+            n = min(len(arr), nrows)
 
             # TODO: At least *some* of this logic is mostly redundant with the
             # _convert_foo methods in this class; see if we can eliminate some
             # of that duplication.
 
-            if not n:
-                # The input column had an empty array, so just use the fill
-                # value
-                continue
-
             field = _get_recarray_field(data, idx)
             name = column.name
             fitsformat = column.format
diff --git a/docs/changes/io.fits/16505.bugfix.rst b/docs/changes/io.fits/16505.bugfix.rst
new file mode 100644
index 0000000000..384bdc0672
--- /dev/null
+++ b/docs/changes/io.fits/16505.bugfix.rst
@@ -0,0 +1,2 @@
+Ensure that also zero-length tables preserve whether integer data are
+signed or unsigned.


Test patch:
diff --git a/astropy/io/fits/tests/test_connect.py b/astropy/io/fits/tests/test_connect.py
index 92f6901266..5b07be092b 100644
--- a/astropy/io/fits/tests/test_connect.py
+++ b/astropy/io/fits/tests/test_connect.py
@@ -1102,3 +1102,17 @@ def test_null_propagation_in_table_read(tmp_path):
     # equal to NULL_VALUE
     t = Table.read(output_filename)
     assert t["a"].fill_value == NULL_VALUE
+
+
+def test_unsigned_int_dtype_propagation_for_zero_length_table():
+    # Regression test for gh-16501
+    tbl = Table(
+        [
+            Column(name="unsigned16", dtype="uint16"),
+            Column(name="unsigned32", dtype="uint32"),
+            Column(name="unsigned64", dtype="uint64"),
+        ]
+    )
+    hdu = BinTableHDU(tbl)
+    tbl2 = Table.read(hdu)
+    assert tbl.dtype == tbl2.dtype