"""Machine limits for Float32 and Float64 and (long double) if available...
"""
__all__ = ['finfo', 'iinfo']
import math
import types
import warnings
from functools import cached_property
from numpy._utils import set_module
from . import numeric, numerictypes as ntypes
from ._multiarray_umath import _populate_finfo_constants
def _fr0(a):
"""fix rank-0 --> rank-1"""
if a.ndim == 0:
a = a.copy()
a.shape = (1,)
return a
def _fr1(a):
"""fix rank > 0 --> rank-0"""
if a.size == 1:
a = a.copy()
a.shape = ()
return a
_convert_to_float = {
ntypes.csingle: ntypes.single,
ntypes.complex128: ntypes.float64,
ntypes.clongdouble: ntypes.longdouble
}
# Parameters for creating MachAr / MachAr-like objects
_title_fmt = 'numpy {} precision floating point number'
_MACHAR_PARAMS = {
ntypes.double: {
'itype': ntypes.int64,
'fmt': '%24.16e',
'title': _title_fmt.format('double')},
ntypes.single: {
'itype': ntypes.int32,
'fmt': '%15.7e',
'title': _title_fmt.format('single')},
ntypes.longdouble: {
'itype': ntypes.longlong,
'fmt': '%s',
'title': _title_fmt.format('long double')},
ntypes.half: {
'itype': ntypes.int16,
'fmt': '%12.5e',
'title': _title_fmt.format('half')}}
@set_module('numpy')
class finfo:
"""
finfo(dtype)
Machine limits for floating point types.
Attributes
----------
bits : int
The number of bits occupied by the type.
dtype : dtype
Returns the dtype for which `finfo` returns information. For complex
input, the returned dtype is the associated ``float*`` dtype for its
real and complex components.
eps : float
The difference between 1.0 and the next smallest representable float
larger than 1.0. For example, for 64-bit binary floats in the IEEE-754
standard, ``eps = 2**-52``, approximately 2.22e-16.
epsneg : float
The difference between 1.0 and the next smallest representable float
less than 1.0. For example, for 64-bit binary floats in the IEEE-754
standard, ``epsneg = 2**-53``, approximately 1.11e-16.
iexp : int
The number of bits in the exponent portion of the floating point
representation.
machep : int
The exponent that yields `eps`.
max : floating point number of the appropriate type
The largest representable number.
maxexp : int
The smallest positive power of the base (2) that causes overflow.
Corresponds to the C standard MAX_EXP.
min : floating point number of the appropriate type
The smallest representable number, typically ``-max``.
minexp : int
The most negative power of the base (2) consistent with there
being no leading 0's in the mantissa. Corresponds to the C
standard MIN_EXP - 1.
negep : int
The exponent that yields `epsneg`.
nexp : int
The number of bits in the exponent including its sign and bias.
nmant : int
The number of explicit bits in the mantissa (excluding the implicit
leading bit for normalized numbers).
precision : int
The approximate number of decimal digits to which this kind of
float is precise.
resolution : floating point number of the appropriate type
The approximate decimal resolution of this type, i.e.,
``10**-precision``.
tiny : float
An alias for `smallest_normal`, kept for backwards compatibility.
smallest_normal : float
The smallest positive floating point number with 1 as leading bit in
the mantissa following IEEE-754 (see Notes).
smallest_subnormal : float
The smallest positive floating point number with 0 as leading bit in
the mantissa following IEEE-754.
Parameters
----------
dtype : float, dtype, or instance
Kind of floating point or complex floating point
data-type about which to get information.
See Also
--------
iinfo : The equivalent for integer data types.
spacing : The distance between a value and the nearest adjacent number
nextafter : The next floating point value after x1 towards x2
Notes
-----
For developers of NumPy: do not instantiate this at the module level.
The initial calculation of these parameters is expensive and negatively
impacts import times. These objects are cached, so calling ``finfo()``
repeatedly inside your functions is not a problem.
Note that ``smallest_normal`` is not actually the smallest positive
representable value in a NumPy floating point type. As in the IEEE-754
standard [1]_, NumPy floating point types make use of subnormal numbers to
fill the gap between 0 and ``smallest_normal``. However, subnormal numbers
may have significantly reduced precision [2]_.
For ``longdouble``, the representation varies across platforms. On most
platforms it is IEEE 754 binary128 (quad precision) or binary64-extended
(80-bit extended precision). On PowerPC systems, it may use the IBM
double-double format (a pair of float64 values), which has special
characteristics for precision and range.
This function can also be used for complex data types as well. If used,
the output will be the same as the corresponding real float type
(e.g. numpy.finfo(numpy.csingle) is the same as numpy.finfo(numpy.single)).
However, the output is true for the real and imaginary components.
References
----------
.. [1] IEEE Standard for Floating-Point Arithmetic, IEEE Std 754-2008,
pp.1-70, 2008, https://doi.org/10.1109/IEEESTD.2008.4610935
.. [2] Wikipedia, "Denormal Numbers",
https://en.wikipedia.org/wiki/Denormal_number
Examples
--------
>>> import numpy as np
>>> np.finfo(np.float64).dtype
dtype('float64')
>>> np.finfo(np.complex64).dtype
dtype('float32')
"""
_finfo_cache = {}
__class_getitem__ = classmethod(types.GenericAlias)
def __new__(cls, dtype):
try:
obj = cls._finfo_cache.get(dtype) # most common path
if obj is not None:
return obj
except TypeError:
pass
if dtype is None:
# Deprecated in NumPy 1.25, 2023-01-16
warnings.warn(
"finfo() dtype cannot be None. This behavior will "
"raise an error in the future. (Deprecated in NumPy 1.25)",
DeprecationWarning,
stacklevel=2
)
try:
dtype = numeric.dtype(dtype)
except TypeError:
# In case a float instance was given
dtype = numeric.dtype(type(dtype))
obj = cls._finfo_cache.get(dtype)
if obj is not None:
return obj
dtypes = [dtype]
newdtype = ntypes.obj2sctype(dtype)
if newdtype is not dtype:
dtypes.append(newdtype)
dtype = newdtype
if not issubclass(dtype, numeric.inexact):
raise ValueError(f"data type {dtype!r} not inexact")
obj = cls._finfo_cache.get(dtype)
if obj is not None:
return obj
if not issubclass(dtype, numeric.floating):
newdtype = _convert_to_float[dtype]
if newdtype is not dtype:
# dtype changed, for example from complex128 to float64
dtypes.append(newdtype)
dtype = newdtype
obj = cls._finfo_cache.get(dtype, None)
if obj is not None:
# the original dtype was not in the cache, but the new
# dtype is in the cache. we add the original dtypes to
# the cache and return the result
for dt in dtypes:
cls._finfo_cache[dt] = obj
return obj
obj = object.__new__(cls)._init(dtype)
for dt in dtypes:
cls._finfo_cache[dt] = obj
return obj
def _init(self, dtype):
self.dtype = numeric.dtype(dtype)
self.bits = self.dtype.itemsize * 8
self._fmt = None
self._repr = None
_populate_finfo_constants(self, self.dtype)
return self
[docs]
@cached_property
def epsneg(self):
# Assume typical floating point logic. Could also use nextafter.
return self.eps / self._radix
[docs]
@cached_property
def resolution(self):
return self.dtype.type(10)**-self.precision
[docs]
@cached_property
def machep(self):
return int(math.log2(self.eps))
[docs]
@cached_property
def negep(self):
return int(math.log2(self.epsneg))
[docs]
@cached_property
def nexp(self):
# considering all ones (inf/nan) and all zeros (subnormal/zero)
return math.ceil(math.log2(self.maxexp - self.minexp + 2))
[docs]
@cached_property
def iexp(self):
# Calculate exponent bits from it's range:
return math.ceil(math.log2(self.maxexp - self.minexp))
def __str__(self):
if (fmt := getattr(self, "_fmt", None)) is not None:
return fmt
def get_str(name, pad=None):
if (val := getattr(self, name, None)) is None:
return "<undefined>"
if pad is not None:
s = str(val).ljust(pad)
return str(val)
precision = get_str("precision", 3)
machep = get_str("machep", 6)
negep = get_str("negep", 6)
minexp = get_str("minexp", 6)
maxexp = get_str("maxexp", 6)
resolution = get_str("resolution")
eps = get_str("eps")
epsneg = get_str("epsneg")
tiny = get_str("tiny")
smallest_normal = get_str("smallest_normal")
smallest_subnormal = get_str("smallest_subnormal")
nexp = get_str("nexp", 6)
max_ = get_str("max")
if hasattr(self, "min") and hasattr(self, "max") and -self.min == self.max:
min_ = "-max"
else:
min_ = get_str("min")
fmt = (
f'Machine parameters for {self.dtype}\n'
f'---------------------------------------------------------------\n'
f'precision = {precision} resolution = {resolution}\n'
f'machep = {machep} eps = {eps}\n'
f'negep = {negep} epsneg = {epsneg}\n'
f'minexp = {minexp} tiny = {tiny}\n'
f'maxexp = {maxexp} max = {max_}\n'
f'nexp = {nexp} min = {min_}\n'
f'smallest_normal = {smallest_normal} '
f'smallest_subnormal = {smallest_subnormal}\n'
f'---------------------------------------------------------------\n'
)
self._fmt = fmt
return fmt
def __repr__(self):
if (repr_str := getattr(self, "_repr", None)) is not None:
return repr_str
c = self.__class__.__name__
# Use precision+1 digits in exponential notation
fmt_str = _MACHAR_PARAMS.get(self.dtype.type, {}).get('fmt', '%s')
if fmt_str != '%s' and hasattr(self, 'max') and hasattr(self, 'min'):
max_str = (fmt_str % self.max).strip()
min_str = (fmt_str % self.min).strip()
else:
max_str = str(self.max)
min_str = str(self.min)
resolution_str = str(self.resolution)
repr_str = (f"{c}(resolution={resolution_str}, min={min_str},"
f" max={max_str}, dtype={self.dtype})")
self._repr = repr_str
return repr_str
[docs]
@cached_property
def tiny(self):
"""Return the value for tiny, alias of smallest_normal.
Returns
-------
tiny : float
Value for the smallest normal, alias of smallest_normal.
Warns
-----
UserWarning
If the calculated value for the smallest normal is requested for
double-double.
"""
return self.smallest_normal
@set_module('numpy')
class iinfo:
"""
iinfo(type)
Machine limits for integer types.
Attributes
----------
bits : int
The number of bits occupied by the type.
dtype : dtype
Returns the dtype for which `iinfo` returns information.
min : int
The smallest integer expressible by the type.
max : int
The largest integer expressible by the type.
Parameters
----------
int_type : integer type, dtype, or instance
The kind of integer data type to get information about.
See Also
--------
finfo : The equivalent for floating point data types.
Examples
--------
With types:
>>> import numpy as np
>>> ii16 = np.iinfo(np.int16)
>>> ii16.min
-32768
>>> ii16.max
32767
>>> ii32 = np.iinfo(np.int32)
>>> ii32.min
-2147483648
>>> ii32.max
2147483647
With instances:
>>> ii32 = np.iinfo(np.int32(10))
>>> ii32.min
-2147483648
>>> ii32.max
2147483647
"""
_min_vals = {}
_max_vals = {}
__class_getitem__ = classmethod(types.GenericAlias)
def __init__(self, int_type):
try:
self.dtype = numeric.dtype(int_type)
except TypeError:
self.dtype = numeric.dtype(type(int_type))
self.kind = self.dtype.kind
self.bits = self.dtype.itemsize * 8
self.key = "%s%d" % (self.kind, self.bits)
if self.kind not in 'iu':
raise ValueError(f"Invalid integer data type {self.kind!r}.")
@property
def min(self):
"""Minimum value of given dtype."""
if self.kind == 'u':
return 0
else:
try:
val = iinfo._min_vals[self.key]
except KeyError:
val = int(-(1 << (self.bits - 1)))
iinfo._min_vals[self.key] = val
return val
@property
def max(self):
"""Maximum value of given dtype."""
try:
val = iinfo._max_vals[self.key]
except KeyError:
if self.kind == 'u':
val = int((1 << self.bits) - 1)
else:
val = int((1 << (self.bits - 1)) - 1)
iinfo._max_vals[self.key] = val
return val
def __str__(self):
"""String representation."""
fmt = (
'Machine parameters for %(dtype)s\n'
'---------------------------------------------------------------\n'
'min = %(min)s\n'
'max = %(max)s\n'
'---------------------------------------------------------------\n'
)
return fmt % {'dtype': self.dtype, 'min': self.min, 'max': self.max}
def __repr__(self):
return "%s(min=%s, max=%s, dtype=%s)" % (self.__class__.__name__,
self.min, self.max, self.dtype)