Datatypes: get back 80-bit long double, 80-bit float support in general,

hi, sorry if silly question, I have! searched before asking,

from another application ( gnumeric ) I managed to ‘call’ python, do some calculation, and get back a result, with IEEE doubles. When trying to use 80-bit long doubles I can convert them to e.g. numpy longdoubles via longdouble( str( x ) ) or similar, but the return value is discarded as ‘Unsupported Python type: <class ‘numpy.float128’>’.

I have searched deep enough that numpy uses a 80-bit structure for the value, zero-padded to 128 bits for alignment, the question is how to return only the 80 bits to be able to read into a ‘C’ 80-bit long double.

I’m not bounded to numpy, it’s just what crossed my way, the request is have 80-bit long doubles, read in to python without loosing precision, calculate something, return 80-bit long double(s).

Compiler: gcc, OS: linux,

TIA for any help

( For those who would object what nonsensical exotic stuff I am trying to do here: I’m trying to check double calculations against long double, and ‘C’ / gnumeric calculations against python. )

Maybe you can write a C program that does the 80 bit calculations and prints the results.
Then run that program as a subprocess from python, read the results and use that to compare?

What do you mean about “checking” the calculations, exactly? Do you need to preserve all the precision and range from the long-double values? What actual code have you tried working with so far? I’m not familiar with gnumeric, so I can’t really visualize the process you have in mind.

hi, thanks for answering, if I had to do that I could directly use ‘C’, the wish is:
A. compare to calculations made by another system,
B. not to have too many hassles with it,
C. I could imagine an option to return a 80-bit long double from python could be useful for others too, evtl. already implemented somewhere I didn’t find, and if not not too complicated to implement in python but beyond my skills,

hi, thanks for your interest,
I just started to learn and like python, I’m working on the idea to improve bin-FP-math towards human / decimal / school math compatibility, and have seen some things different in python vs. C vs. gnumeric, which helps identifying hot spots and cherry picking solutions.
In the same way comparing double vs. long double calculations helps spotting problems and working on them.
But that’s not the focus of the question, that’s simply:
python numpy uses 128-bit zero padded values as long doubles instead of 80-bit, is there a.) any option to return 80-bit structures, or b.) another module which can do?
Another aspect is to learn about gnumeric - python interactivity, gnumeric has ‘plugins’ to call external calculations from e.g. python, and an interface ‘introspection’ to access spreadsheet data from python, that’s not widely used, but may gain importance since Excel aquired python.

I think you’d be interested in gmpy2 (wrapper around the GNU MultiPrecision library).
See:
https://gmpy2.readthedocs.io/en/latest/contexts.html#gmpy2.ieee

>>> from math import pi
>>> from gmpy2 import *
>>> set_context(ieee(128))
>>> get_context()
context(precision=113, real_prec=Default, imag_prec=Default,
        round=RoundToNearest, real_round=Default, imag_round=Default,
        emax=16384, emin=-16493,
        subnormalize=True,
        trap_underflow=False, underflow=False,
        trap_overflow=False, overflow=False,
        trap_inexact=False, inexact=False,
        trap_invalid=False, invalid=False,
        trap_erange=False, erange=False,
        trap_divzero=False, divzero=False,
        allow_complex=False,
        rational_division=False,
        allow_release_gil=False)
>>> pi
3.141592653589793
>>> x = mpfr(pi)
>>> x
mpfr('3.14159265358979311599796346854418516',113)
1 Like

hello and thank you, yes, things like that are interesting.
Consider a deviation of ~1.2246467991473532E-16 in the double representation of pi(), then after ~2.5653050788007548E16 revolutions we are at the opposite site of the circle, with each next representable double value flipping to opposite site again. Rendering trigonometric calculations useless, but they are performed in modern spreadsheets and by dummie users.
To demonstrate such things I’d like an external source with better precision, and for speed to get the results automatically rather than e.g. copy-paste from WolframAlpha.
Assume your example calculates float128’s ( precision 113 - bits? )?
But provides a string as result ( mpfr('3.1415926535… )?
Later I may learn how to handle strings or float128 structures in gnumeric, for the moment it would be sufficient - and much easier I hope - if I could get back an 80-bit float which gnumeric-long can use directly.
Assume the first or last 80 bit of the 128-bit structure in numpy match what’s needed for the ‘typical’ long double, and assume conversion isn’t more than copying them to a 80-bit structure, in C-code a ‘union’ of either float128 and float80, float16, float32, or float128 and float32, float16, float80?, but it’s beyond my skills to find in code and modify python.
Thus the original question remains open.

The popular mpmath extension module for Python is something you should look into. It implements binary floating point with user-settable arbitrary precision, and supports many functions. If you have gmpy2 installed (a Python wrapper for the hyper-optimized GMP library), mpmath will automatically use it to do very-high-precision calculation.

>>> import mpmath
>>> print(mpmath.mp) # 53 bits of precision by default
Mpmath settings:
  mp.prec = 53                [default: 53]
  mp.dps = 15                 [default: 15]
  mp.trap_complex = False     [default: False]
>>> mpmath.mpf("0.1")
mpf('0.10000000000000001')
>>> mpmath.mp.prec = 80 # boost precision to 80 bits
>>> mpmath.mpf("0.1")
mpf('0.10000000000000000000000002')
>>> mpmath.mp.prec = 200 # boost precisin to 200 bits
>>> mpmath.mpf("0.1")
mpf('0.10000000000000000000000000000000000000000000000000000000000002')
>>> mpmath.sin(_)
mpf('0.099833416646828152306814198410622026989915388017982259992766909')

The easiest way to move floats across systems is as decimal strings. Working at the bit level with oddball binary formats is just begging for worlds of needless pain. You can get away with that for bog standard 64-bit IEEE-754 double-precision floats (which almost all machines natively support now), but even then you can get in trouble if you’re moving between “big endian” and “little endian” machines. Stick to decimal strings unless you know exactly what you’re doing.

3 Likes

hello @Tim Peters, and thank you for your hints,

it’s double faced, pro: strings are somewhat more clear and easy to understand, con: they need two conversions, different rounding strategies may apply … that would inject additional layers which I’d like to avoid.

Long doubles are somewhat outdated, but still quite common, and useful to spot weaknesses in double calculations. As - if I understood right - numpy uses just this format for it’s longdouble calculations and - if I understood right - only wraps it in padded 128 bit for mem alignment, it would be a good idea to enable export / return of ‘native’ 80-bit values, less error layers for me, and more speed if someone uses it in real applications.

Just for curiosity, if you can point me to code where python / numpy produces / formats it’s return values I’d like to have a look.

I think the point of using string representations is that those are totally unambiguous and leave no room for hidden differences or truncations. Rather than looking at the numpy C code, perhaps the unit test code would be more useful? See:

This is all far more complicated than you’ve realized so far. Even restricted just to numpy, all the possibilities amount to a cross-platform nightmare. Here from the numpy docs:

Python’s floating-point numbers are usually 64-bit floating-point numbers, nearly equivalent to np.float64. In some unusual situations it may be useful to use floating-point numbers with more precision. Whether this is possible in numpy depends on the hardware and on the development environment: specifically, x86 machines provide hardware floating-point with 80-bit precision, and while most C compilers provide this as their long double type, MSVC (standard for Windows builds) makes long double identical to double (64 bits). NumPy makes the compiler’s long double available as np.longdouble (and np.clongdouble for the complex numbers). You can find out what your numpy provides with np.finfo(np.longdouble).

NumPy does not provide a dtype with more precision than C’s long double; in particular, the 128-bit IEEE quad precision data type (FORTRAN’s REAL*16) is not available.

For efficient memory alignment, np.longdouble is usually stored padded with zero bits, either to 96 or 128 bits. Which is more efficient depends on hardware and development environment; typically on 32-bit systems they are padded to 96 bits, while on 64-bit systems they are typically padded to 128 bits. np.longdouble is padded to the system default; np.float96 and np.float128 are provided for users who want specific padding. In spite of the names, np.float96 and np.float128 provide only as much precision as np.longdouble, that is, 80 bits on most x86 machines and 64 bits in standard Windows builds.

Be warned that even if np.longdouble offers more precision than python float, it is easy to lose that extra precision, since python often forces values to pass through float. For example, the % formatting operator requires its arguments to be converted to standard python types, and it is therefore impossible to preserve extended precision even if many decimal places are requested. It can be useful to test your code with the value 1 + np.finfo(np.longdouble).eps.

My advice to stick to decimal strings, when working with oddball float formats, wasn’t casual or uninformed :wink:. If you want to work with custom binary float precisions, as already explained mpmath makes that easy in Python.

For the future, there is no constituency for oddball float precisions among CPython core developers, and it would be a miracle if that changed. It’s self-evident (from history) that not even the numpy folks have cared enough to contribute relevant changes to the CPython core. And, no, I’m not going to give my time to it either - while I routinely use high-precision floats (both binary and decimal), current packages already make that easy.

I’m not clear on what you’re asking there. The word “format” doesn’t really make sense to me unless you’re talking about formatting a float as a string. That code is very long and complex. For example, CPython’s Python/dtoa.c is close to 3 thousand lines of some of the most demanding C code you’ll ever see.

2 Likes

hello @Tim Peters, thanks for all the hints,

Let me answer bottom first to give something back:
dtoa.c from David Gay is somewhat outdated, see:

some other discussions about similar attempts from Grisu / Dragon,
Schubfach, swift, Dragonbox, Drachennest …

sorry, the last one is long, in the end Morten managed to integrate
ryu into goffice which might serve as a template.

Reg. my original question: my ‘use case’ is not to ‘do my work’,
but constructing improvements to achieve more ‘decimal like’
results with doubles and long doubles. That works to some
degree, but could like testing. As I have seen python handling
some points - e.g. rounding - different to others it’s a good
inspiring cross check.

Perhaps sometimes someone will work in that area and can
care for this option, up then I’ll try to use strings and other
workarounds. I just did not want to ‘work around’ while evtl. a
simple solution is available and I just didn’t find.

hello @Hans Geuns-Meyer, hello @Tim Peters,

thanks for the code pointer, alas it didn’t yet raise my wisdom.

Can ‘better’ formulate seeking where numpy ‘formats’ it output
for longdouble to 128 bits. Either it calculates with 80-bit structures
( think that is the case ), and then pads the result to 128 bit.
Or it calculates with 128 bit, and then set’s the last bits to zero.

That point I’d like to know as possibly it’s easy to intercept there
and get a ‘classic’ 80-bit longdouble back.

I would really heed (and reread) @tim.one suggestions above - if anyone knows this kind of stuff intimately, it is him :slight_smile:

Numpy has a truly byzantine system of datatypes, but if this is any help:

yes, did that, think I understood most of it, still consider it a shortcoming in
compatibility to other systems not to return ‘native float80’, but a more exotic
one and understand that there is no prio.

there they show up, :slight_smile: , will try to dig down, but will take some time as I need to
learn about numpy coding first.

Thank you.