0

Is there a way to determine if a number is storable as a single-float or double-float?
I'm implementing my own database format and in my helper function num2bin(n) I currently have the follwoing code:

import struct, warnings

def num2bin(n:int|float) -> bytes:
    match type(n).__name__:
        case 'int':
            ... # Excluded because unimportant to this question 
        case 'float':
            try:
                return struct.pack('>f', n)
            except struct.error:
                try:
                    return struct.pack('>d', n)
                except struct.error:
                    warnings.warn(f"num2bin(n): Failed to store {n} as float or double")
                    return b''
        case other:
            warnings.warn(f"num2bin(n): 'n' is of type '{other}' (must be 'int' or 'float')")
            return b''

Is there a better and more elegant way to determine whether I can store the number as float (f) or double (d)?
I think there must be a better way to do that than just try it and catching errors?

Lampe2020
  • 115
  • 1
  • 12
  • 1
    Try and catch are considered very Pythonic. There's even a name for it: [EAFP](https://stackoverflow.com/q/11360858/5987). – Mark Ransom Aug 14 '23 at 12:32
  • @MarkRansom My problem isn't its "pythonicness", it's just that it looks very unelegant to me, especially with the nested `try…catch`s. – Lampe2020 Aug 14 '23 at 12:39
  • @Lampe2020, Perhaps convert number to a `double x`. Then convert `x` to `float y`. If `x == y`, the savable as `float` with no precession/range issues. – chux - Reinstate Monica Aug 15 '23 at 19:23

1 Answers1

0

I am not entirely sure if it will include all floats, but I now put these checks:

import struct, warnings

def num2bin(n:int|float) -> bytes:
    match type(n).__name__:
        case 'int':
            ... # Excluded because unimportant to this answer 
        case 'float':
            fltmax:tuple[float] = struct.unpack(
                '>ff',
                b'\x019Dp\x7f\x7f\xff\xff'
                # Min and max single-precision float value (2.2250738585072014e-308, 1.7976931348623157e+308)
            )
            dblmax:tuple[float] = struct.unpack(
                '>dd',
                b'\x00\x10\x00\x00\x00\x00\x00\x00\x7f\xef\xff\xff\xff\xff\xff\xff'
                # Min and max double-precision float value (3.402823507664669e-38, 3.4028234663852886e+38)
            )
            if (n>fltmax[0])and(fltmax[1]>n):
                return struct.pack('>f', n)
            elif (n>dblmax[0])and(dblmax[1]>n):
                return struct.pack('>d', n)
            else:
                warnings.warn(f"num2bin(n): Failed to store {n} as float or double")
                return b''
        case other:
            warnings.warn(f"num2bin(n): 'n' is of type '{other}' (must be 'int' or 'float')")
            return b''

Because floating-point numbers can both be large and unprecise, as well as small and precise, I'm not sure if that includes all floats or will fail for some that actually are storable as float or double...

Lampe2020
  • 115
  • 1
  • 12
  • 1
    This probably delivers the same result as your original. Neither one considers the precision requirements of the number. – Mark Ransom Aug 14 '23 at 12:34