Spaces:
Running
Running
"""Machine limits for Float32 and Float64 and (long double) if available... | |
""" | |
__all__ = ['finfo', 'iinfo'] | |
import warnings | |
from .machar import MachAr | |
from .overrides import set_module | |
from . import numeric | |
from . import numerictypes as ntypes | |
from .numeric import array, inf | |
from .umath import log10, exp2 | |
from . import umath | |
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 | |
class MachArLike: | |
""" Object to simulate MachAr instance """ | |
def __init__(self, | |
ftype, | |
*, eps, epsneg, huge, tiny, ibeta, **kwargs): | |
params = _MACHAR_PARAMS[ftype] | |
float_conv = lambda v: array([v], ftype) | |
float_to_float = lambda v : _fr1(float_conv(v)) | |
float_to_str = lambda v: (params['fmt'] % array(_fr0(v)[0], ftype)) | |
self.title = params['title'] | |
# Parameter types same as for discovered MachAr object. | |
self.epsilon = self.eps = float_to_float(eps) | |
self.epsneg = float_to_float(epsneg) | |
self.xmax = self.huge = float_to_float(huge) | |
self.xmin = self.tiny = float_to_float(tiny) | |
self.ibeta = params['itype'](ibeta) | |
self.__dict__.update(kwargs) | |
self.precision = int(-log10(self.eps)) | |
self.resolution = float_to_float(float_conv(10) ** (-self.precision)) | |
self._str_eps = float_to_str(self.eps) | |
self._str_epsneg = float_to_str(self.epsneg) | |
self._str_xmin = float_to_str(self.xmin) | |
self._str_xmax = float_to_str(self.xmax) | |
self._str_resolution = float_to_str(self.resolution) | |
_convert_to_float = { | |
ntypes.csingle: ntypes.single, | |
ntypes.complex_: ntypes.float_, | |
ntypes.clongfloat: ntypes.longfloat | |
} | |
# Parameters for creating MachAr / MachAr-like objects | |
_title_fmt = 'numpy {} precision floating point number' | |
_MACHAR_PARAMS = { | |
ntypes.double: dict( | |
itype = ntypes.int64, | |
fmt = '%24.16e', | |
title = _title_fmt.format('double')), | |
ntypes.single: dict( | |
itype = ntypes.int32, | |
fmt = '%15.7e', | |
title = _title_fmt.format('single')), | |
ntypes.longdouble: dict( | |
itype = ntypes.longlong, | |
fmt = '%s', | |
title = _title_fmt.format('long double')), | |
ntypes.half: dict( | |
itype = ntypes.int16, | |
fmt = '%12.5e', | |
title = _title_fmt.format('half'))} | |
# Key to identify the floating point type. Key is result of | |
# ftype('-0.1').newbyteorder('<').tobytes() | |
# See: | |
# https://perl5.git.perl.org/perl.git/blob/3118d7d684b56cbeb702af874f4326683c45f045:/Configure | |
_KNOWN_TYPES = {} | |
def _register_type(machar, bytepat): | |
_KNOWN_TYPES[bytepat] = machar | |
_float_ma = {} | |
def _register_known_types(): | |
# Known parameters for float16 | |
# See docstring of MachAr class for description of parameters. | |
f16 = ntypes.float16 | |
float16_ma = MachArLike(f16, | |
machep=-10, | |
negep=-11, | |
minexp=-14, | |
maxexp=16, | |
it=10, | |
iexp=5, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=exp2(f16(-10)), | |
epsneg=exp2(f16(-11)), | |
huge=f16(65504), | |
tiny=f16(2 ** -14)) | |
_register_type(float16_ma, b'f\xae') | |
_float_ma[16] = float16_ma | |
# Known parameters for float32 | |
f32 = ntypes.float32 | |
float32_ma = MachArLike(f32, | |
machep=-23, | |
negep=-24, | |
minexp=-126, | |
maxexp=128, | |
it=23, | |
iexp=8, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=exp2(f32(-23)), | |
epsneg=exp2(f32(-24)), | |
huge=f32((1 - 2 ** -24) * 2**128), | |
tiny=exp2(f32(-126))) | |
_register_type(float32_ma, b'\xcd\xcc\xcc\xbd') | |
_float_ma[32] = float32_ma | |
# Known parameters for float64 | |
f64 = ntypes.float64 | |
epsneg_f64 = 2.0 ** -53.0 | |
tiny_f64 = 2.0 ** -1022.0 | |
float64_ma = MachArLike(f64, | |
machep=-52, | |
negep=-53, | |
minexp=-1022, | |
maxexp=1024, | |
it=52, | |
iexp=11, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=2.0 ** -52.0, | |
epsneg=epsneg_f64, | |
huge=(1.0 - epsneg_f64) / tiny_f64 * f64(4), | |
tiny=tiny_f64) | |
_register_type(float64_ma, b'\x9a\x99\x99\x99\x99\x99\xb9\xbf') | |
_float_ma[64] = float64_ma | |
# Known parameters for IEEE 754 128-bit binary float | |
ld = ntypes.longdouble | |
epsneg_f128 = exp2(ld(-113)) | |
tiny_f128 = exp2(ld(-16382)) | |
# Ignore runtime error when this is not f128 | |
with numeric.errstate(all='ignore'): | |
huge_f128 = (ld(1) - epsneg_f128) / tiny_f128 * ld(4) | |
float128_ma = MachArLike(ld, | |
machep=-112, | |
negep=-113, | |
minexp=-16382, | |
maxexp=16384, | |
it=112, | |
iexp=15, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=exp2(ld(-112)), | |
epsneg=epsneg_f128, | |
huge=huge_f128, | |
tiny=tiny_f128) | |
# IEEE 754 128-bit binary float | |
_register_type(float128_ma, | |
b'\x9a\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\xfb\xbf') | |
_register_type(float128_ma, | |
b'\x9a\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\xfb\xbf') | |
_float_ma[128] = float128_ma | |
# Known parameters for float80 (Intel 80-bit extended precision) | |
epsneg_f80 = exp2(ld(-64)) | |
tiny_f80 = exp2(ld(-16382)) | |
# Ignore runtime error when this is not f80 | |
with numeric.errstate(all='ignore'): | |
huge_f80 = (ld(1) - epsneg_f80) / tiny_f80 * ld(4) | |
float80_ma = MachArLike(ld, | |
machep=-63, | |
negep=-64, | |
minexp=-16382, | |
maxexp=16384, | |
it=63, | |
iexp=15, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=exp2(ld(-63)), | |
epsneg=epsneg_f80, | |
huge=huge_f80, | |
tiny=tiny_f80) | |
# float80, first 10 bytes containing actual storage | |
_register_type(float80_ma, b'\xcd\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xfb\xbf') | |
_float_ma[80] = float80_ma | |
# Guessed / known parameters for double double; see: | |
# https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format#Double-double_arithmetic | |
# These numbers have the same exponent range as float64, but extended number of | |
# digits in the significand. | |
huge_dd = (umath.nextafter(ld(inf), ld(0)) | |
if hasattr(umath, 'nextafter') # Missing on some platforms? | |
else float64_ma.huge) | |
float_dd_ma = MachArLike(ld, | |
machep=-105, | |
negep=-106, | |
minexp=-1022, | |
maxexp=1024, | |
it=105, | |
iexp=11, | |
ibeta=2, | |
irnd=5, | |
ngrd=0, | |
eps=exp2(ld(-105)), | |
epsneg= exp2(ld(-106)), | |
huge=huge_dd, | |
tiny=exp2(ld(-1022))) | |
# double double; low, high order (e.g. PPC 64) | |
_register_type(float_dd_ma, | |
b'\x9a\x99\x99\x99\x99\x99Y<\x9a\x99\x99\x99\x99\x99\xb9\xbf') | |
# double double; high, low order (e.g. PPC 64 le) | |
_register_type(float_dd_ma, | |
b'\x9a\x99\x99\x99\x99\x99\xb9\xbf\x9a\x99\x99\x99\x99\x99Y<') | |
_float_ma['dd'] = float_dd_ma | |
def _get_machar(ftype): | |
""" Get MachAr instance or MachAr-like instance | |
Get parameters for floating point type, by first trying signatures of | |
various known floating point types, then, if none match, attempting to | |
identify parameters by analysis. | |
Parameters | |
---------- | |
ftype : class | |
Numpy floating point type class (e.g. ``np.float64``) | |
Returns | |
------- | |
ma_like : instance of :class:`MachAr` or :class:`MachArLike` | |
Object giving floating point parameters for `ftype`. | |
Warns | |
----- | |
UserWarning | |
If the binary signature of the float type is not in the dictionary of | |
known float types. | |
""" | |
params = _MACHAR_PARAMS.get(ftype) | |
if params is None: | |
raise ValueError(repr(ftype)) | |
# Detect known / suspected types | |
key = ftype('-0.1').newbyteorder('<').tobytes() | |
ma_like = None | |
if ftype == ntypes.longdouble: | |
# Could be 80 bit == 10 byte extended precision, where last bytes can | |
# be random garbage. | |
# Comparing first 10 bytes to pattern first to avoid branching on the | |
# random garbage. | |
ma_like = _KNOWN_TYPES.get(key[:10]) | |
if ma_like is None: | |
ma_like = _KNOWN_TYPES.get(key) | |
if ma_like is not None: | |
return ma_like | |
# Fall back to parameter discovery | |
warnings.warn( | |
'Signature {} for {} does not match any known type: ' | |
'falling back to type probe function'.format(key, ftype), | |
UserWarning, stacklevel=2) | |
return _discovered_machar(ftype) | |
def _discovered_machar(ftype): | |
""" Create MachAr instance with found information on float types | |
""" | |
params = _MACHAR_PARAMS[ftype] | |
return MachAr(lambda v: array([v], ftype), | |
lambda v:_fr0(v.astype(params['itype']))[0], | |
lambda v:array(_fr0(v)[0], ftype), | |
lambda v: params['fmt'] % array(_fr0(v)[0], ftype), | |
params['title']) | |
class finfo: | |
""" | |
finfo(dtype) | |
Machine limits for floating point types. | |
Attributes | |
---------- | |
bits : int | |
The number of bits occupied by the type. | |
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. | |
machar : MachAr | |
The object which calculated these parameters and holds more | |
detailed information. | |
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. | |
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. | |
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 bits in the mantissa. | |
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 | |
The smallest positive floating point number with full precision | |
(see Notes). | |
Parameters | |
---------- | |
dtype : float, dtype, or instance | |
Kind of floating point data-type about which to get information. | |
See Also | |
-------- | |
MachAr : The implementation of the tests that produce this information. | |
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 ``tiny`` 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 ``tiny``. However, subnormal numbers may have | |
significantly reduced precision [2]_. | |
References | |
---------- | |
.. [1] IEEE Standard for Floating-Point Arithmetic, IEEE Std 754-2008, | |
pp.1-70, 2008, http://www.doi.org/10.1109/IEEESTD.2008.4610935 | |
.. [2] Wikipedia, "Denormal Numbers", | |
https://en.wikipedia.org/wiki/Denormal_number | |
""" | |
_finfo_cache = {} | |
def __new__(cls, dtype): | |
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, None) | |
if obj is not None: | |
return obj | |
dtypes = [dtype] | |
newdtype = numeric.obj2sctype(dtype) | |
if newdtype is not dtype: | |
dtypes.append(newdtype) | |
dtype = newdtype | |
if not issubclass(dtype, numeric.inexact): | |
raise ValueError("data type %r not inexact" % (dtype)) | |
obj = cls._finfo_cache.get(dtype, None) | |
if obj is not None: | |
return obj | |
if not issubclass(dtype, numeric.floating): | |
newdtype = _convert_to_float[dtype] | |
if newdtype is not dtype: | |
dtypes.append(newdtype) | |
dtype = newdtype | |
obj = cls._finfo_cache.get(dtype, None) | |
if obj is not None: | |
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) | |
machar = _get_machar(dtype) | |
for word in ['precision', 'iexp', | |
'maxexp', 'minexp', 'negep', | |
'machep']: | |
setattr(self, word, getattr(machar, word)) | |
for word in ['tiny', 'resolution', 'epsneg']: | |
setattr(self, word, getattr(machar, word).flat[0]) | |
self.bits = self.dtype.itemsize * 8 | |
self.max = machar.huge.flat[0] | |
self.min = -self.max | |
self.eps = machar.eps.flat[0] | |
self.nexp = machar.iexp | |
self.nmant = machar.it | |
self.machar = machar | |
self._str_tiny = machar._str_xmin.strip() | |
self._str_max = machar._str_xmax.strip() | |
self._str_epsneg = machar._str_epsneg.strip() | |
self._str_eps = machar._str_eps.strip() | |
self._str_resolution = machar._str_resolution.strip() | |
return self | |
def __str__(self): | |
fmt = ( | |
'Machine parameters for %(dtype)s\n' | |
'---------------------------------------------------------------\n' | |
'precision = %(precision)3s resolution = %(_str_resolution)s\n' | |
'machep = %(machep)6s eps = %(_str_eps)s\n' | |
'negep = %(negep)6s epsneg = %(_str_epsneg)s\n' | |
'minexp = %(minexp)6s tiny = %(_str_tiny)s\n' | |
'maxexp = %(maxexp)6s max = %(_str_max)s\n' | |
'nexp = %(nexp)6s min = -max\n' | |
'---------------------------------------------------------------\n' | |
) | |
return fmt % self.__dict__ | |
def __repr__(self): | |
c = self.__class__.__name__ | |
d = self.__dict__.copy() | |
d['klass'] = c | |
return (("%(klass)s(resolution=%(resolution)s, min=-%(_str_max)s," | |
" max=%(_str_max)s, dtype=%(dtype)s)") % d) | |
class iinfo: | |
""" | |
iinfo(type) | |
Machine limits for integer types. | |
Attributes | |
---------- | |
bits : int | |
The number of bits occupied by the type. | |
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: | |
>>> 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 = {} | |
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("Invalid integer data type %r." % (self.kind,)) | |
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 | |
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) | |