Floating point precision in fits header keywords

reposting here so that search engines can find this discussion for the future

We’ve noticed a bug/glitch in the way floating point values are written to header cards (in our usecase, through fits.setval). This is probably an underlying glitch in the C library.

Our institution requires having floating-point keyword values in a given precision format.
One said values happens to be -99.9, to be written as %.1f format.

A call to fits.setval(file, ‘D_APDTI’, -99.9) results in the file header showing:
D_APDTI = -99.90000000000001 / APD coolant inlet temperature (degC)
D_APDTI = -99.9 / APD coolant inlet temperature (degC)
is expected.

Writing a string is not appropriate, e.g. fits.setval(file, ‘D_APDTI’, ‘%.2f’ % (-99.9,), as it results in a single-quoted string in the header:
D_APDTI = ‘-99.9’ / APD coolant inlet temperature (degC)

Please let us know of the appropriate way to enforce float formatting, or of a known workaround. This issue may very well be a classic one, and I apologize for the inconvenience in that case.

I think what’s happening happening here is that -99.9 is not an exact representation of a binary number (see 15. Floating Point Arithmetic: Issues and Limitations — Python 3.9.10 documentation). Which version of Python are you using? Some of those formatting issues are supposed to be solved in recent versions.

Also, instead of fits.setval(file, ‘D_APDTI’, -99.9) you could try to set the header directly header['D_APDTI'] = -99.9. I don’t know if that makes a difference; I’ve never looked into code inside of the fits module, but that’s the things I will try.

Here is a minimal example for other trying to reproduce the problem to help with debugging:

from astropy.io import fits

# Just use table to quickly make a short fits file to experiment on
from astropy.table import Table
tab = Table({'a': [1,2]})

fits.setval('text.fits', 'D_APDTI', value=-99.9)
fits.setval('text.fits', 'D_APDTI2', value=23)

Then, outside of Python (e.g. less text.fits), I see:

SIMPLE  =                    T / conforms to FITS standard                      
BITPIX  =                    8 / array data type                                
NAXIS   =                    0 / number of array dimensions                     EXTEND  =                    T                                                  
D_APDTI =   -99.90000000000001                                                  
D_APDTI2=                   23                                                  

So, I can reproduce the problem (Python 3.9, astropy 5.0) but I don’t have a solution.

-99.9 is not the only value where this happens; one can get that with several other numbers. It’s just one of the numbers that happens to off in binary representation just enough to show up here.

I think it’s one of those cases where you can’t have a solution: If you want to control exactly how it’s formatted, you need to use a string. If you can’t use a string, you’ll get the binary representation as good as it gets. Depending on your machine architecture, you might be able to use higher precision numbers to avoid that issue (e.g. numpy.float128), but on my computer 64-bit floats is the best that numpy offers me.

If you disagree and believe that this is a bug that should be fixed, we’d appreciate a bug report on Issues · astropy/astropy · GitHub (feel free to copy my minimal example above into the issue description).

After sleeping on it, I’ve decided to report that as a bug: Floating point precision in fits header keywords · Issue #12954 · astropy/astropy · GitHub

Let’s see what the maintainers of astropy.io.fits think.