are numpys dtypes kept even for non-numpy multiplications/divisions/sums?
Question:
I am working on a simple simulator for a radio frequency application and have to deal with very low complex numbers. During the process I have a Matrix like np.array([[A,B],[C,D]], dtype=np.clongdouble)
which ensures the necessary "resolution(?)". However, I have to do stuff like
den = A+B/z0+C*z0+D
s11 = A+B/z0-C*z0-D)/den
s12 = 2*(A*D-B*C)/den
s21 = 2/den
s22 = (-A+B/z0-C*z0+D)/den
I think Z0
is of type double
since it’s calculated without numpy.
Now I wonder: Do I have to do the calculations of den
etc with numpy to achieve/keep resolution or are the ‘normal’ calculations sufficient enough?
Answers:
Python’s floating-point numbers are usually 64-bit floating-point numbers, nearly equivalent to np.float64.
we can actually see this from the basic types in the docs here:
https://numpy.org/doc/stable/user/basics.types.html
So Python alone is satisfactory for accuracy.
Python’s complex numbers are represented "as a pair of machine-level double precision floating point numbers".
This is typically 2 x 64 = 128 bits.
NumPy’s clongdouble
are represented as a pair of machine-level "extended-precision floating-point numbers".
This is typically 2 x 80 = 160 bits.
However, what NumPy provides for long double
(as per @WarrenWeckesser’s comment) is platform and compiler dependent.
Therefore, the datatype you are using from NumPy may (or may not) have higher precision.
To force a number to have a specific precision, you can just cast it with np.array(..., dtype=...)
:
import numpy as np
a = 1.0j
b = np.array(a, dtype=np.clongdouble)
print(b.dtype, b.nbytes)
# complex256 32
Note that the names here are a bit misleading as the number of bits refer to the alignment and not necessarily to the number of bits actually used for the data.
However, you do not need to do any specific casting for the above formulae to work, as the result of an operation with a higher precision and a lower precision operand would result in a higher precision result:
c = np.array(a)
print(c.dtype, c.nbytes)
# complex128 16
d = b + c
print(d.dtype, d.nbytes)
# complex256 32
e = b + a
print(e.dtype, e.nbytes)
# complex256 32
print(d == e)
# True
Eventually, you may want to compute z0
with NumPy’s higher precision data types, if that precision is available and it is critical to your result.
Note that this table would be your starting point to see how NumPy maps Python data types.
Note on Floating Point information
Unfortunately, you cannot easily get the information on the number of bits for the different data types that Python uses.
You can get some information with sys.getsizeof()
but this will have information on the memory usage of the Python object, which includes Python’s book-keeping information.
However, you can get some information on Python’s float
with sys.float_info
:
import sys
a = 10+1.0j
print(sys.float_info)
# sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
Similarly, you can get NumPy floating-point information with np.finfo()
(note that when feeding complex datatypes, you get the information on the underlying floats):
for dtype in (float, np.float_, np.double, np.cdouble, np.longdouble, np.clongdouble):
print(np.finfo(dtype))
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float128
---------------------------------------------------------------
precision = 18 resolution = 1e-18
machep = -63 eps = 1.084202172485504434e-19
negep = -64 epsneg = 5.42101086242752217e-20
minexp = -16382 tiny = 3.3621031431120935063e-4932
maxexp = 16384 max = 1.189731495357231765e+4932
nexp = 15 min = -max
---------------------------------------------------------------
Machine parameters for float128
---------------------------------------------------------------
precision = 18 resolution = 1e-18
machep = -63 eps = 1.084202172485504434e-19
negep = -64 epsneg = 5.42101086242752217e-20
minexp = -16382 tiny = 3.3621031431120935063e-4932
maxexp = 16384 max = 1.189731495357231765e+4932
nexp = 15 min = -max
---------------------------------------------------------------
I am working on a simple simulator for a radio frequency application and have to deal with very low complex numbers. During the process I have a Matrix like np.array([[A,B],[C,D]], dtype=np.clongdouble)
which ensures the necessary "resolution(?)". However, I have to do stuff like
den = A+B/z0+C*z0+D
s11 = A+B/z0-C*z0-D)/den
s12 = 2*(A*D-B*C)/den
s21 = 2/den
s22 = (-A+B/z0-C*z0+D)/den
I think Z0
is of type double
since it’s calculated without numpy.
Now I wonder: Do I have to do the calculations of den
etc with numpy to achieve/keep resolution or are the ‘normal’ calculations sufficient enough?
Python’s floating-point numbers are usually 64-bit floating-point numbers, nearly equivalent to np.float64.
we can actually see this from the basic types in the docs here:
https://numpy.org/doc/stable/user/basics.types.html
So Python alone is satisfactory for accuracy.
Python’s complex numbers are represented "as a pair of machine-level double precision floating point numbers".
This is typically 2 x 64 = 128 bits.
NumPy’s clongdouble
are represented as a pair of machine-level "extended-precision floating-point numbers".
This is typically 2 x 80 = 160 bits.
However, what NumPy provides for long double
(as per @WarrenWeckesser’s comment) is platform and compiler dependent.
Therefore, the datatype you are using from NumPy may (or may not) have higher precision.
To force a number to have a specific precision, you can just cast it with np.array(..., dtype=...)
:
import numpy as np
a = 1.0j
b = np.array(a, dtype=np.clongdouble)
print(b.dtype, b.nbytes)
# complex256 32
Note that the names here are a bit misleading as the number of bits refer to the alignment and not necessarily to the number of bits actually used for the data.
However, you do not need to do any specific casting for the above formulae to work, as the result of an operation with a higher precision and a lower precision operand would result in a higher precision result:
c = np.array(a)
print(c.dtype, c.nbytes)
# complex128 16
d = b + c
print(d.dtype, d.nbytes)
# complex256 32
e = b + a
print(e.dtype, e.nbytes)
# complex256 32
print(d == e)
# True
Eventually, you may want to compute z0
with NumPy’s higher precision data types, if that precision is available and it is critical to your result.
Note that this table would be your starting point to see how NumPy maps Python data types.
Note on Floating Point information
Unfortunately, you cannot easily get the information on the number of bits for the different data types that Python uses.
You can get some information with sys.getsizeof()
but this will have information on the memory usage of the Python object, which includes Python’s book-keeping information.
However, you can get some information on Python’s float
with sys.float_info
:
import sys
a = 10+1.0j
print(sys.float_info)
# sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
Similarly, you can get NumPy floating-point information with np.finfo()
(note that when feeding complex datatypes, you get the information on the underlying floats):
for dtype in (float, np.float_, np.double, np.cdouble, np.longdouble, np.clongdouble):
print(np.finfo(dtype))
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float64
---------------------------------------------------------------
precision = 15 resolution = 1.0000000000000001e-15
machep = -52 eps = 2.2204460492503131e-16
negep = -53 epsneg = 1.1102230246251565e-16
minexp = -1022 tiny = 2.2250738585072014e-308
maxexp = 1024 max = 1.7976931348623157e+308
nexp = 11 min = -max
---------------------------------------------------------------
Machine parameters for float128
---------------------------------------------------------------
precision = 18 resolution = 1e-18
machep = -63 eps = 1.084202172485504434e-19
negep = -64 epsneg = 5.42101086242752217e-20
minexp = -16382 tiny = 3.3621031431120935063e-4932
maxexp = 16384 max = 1.189731495357231765e+4932
nexp = 15 min = -max
---------------------------------------------------------------
Machine parameters for float128
---------------------------------------------------------------
precision = 18 resolution = 1e-18
machep = -63 eps = 1.084202172485504434e-19
negep = -64 epsneg = 5.42101086242752217e-20
minexp = -16382 tiny = 3.3621031431120935063e-4932
maxexp = 16384 max = 1.189731495357231765e+4932
nexp = 15 min = -max
---------------------------------------------------------------