561 lines
18 KiB
Python
561 lines
18 KiB
Python
|
"""Machine limits for Float32 and Float64 and (long double) if available...
|
||
|
|
||
|
"""
|
||
|
from __future__ import division, absolute_import, print_function
|
||
|
|
||
|
__all__ = ['finfo', 'iinfo']
|
||
|
|
||
|
import warnings
|
||
|
|
||
|
from .machar import MachAr
|
||
|
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
|
||
|
|
||
|
|
||
|
_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'))}
|
||
|
|
||
|
|
||
|
class MachArLike(object):
|
||
|
""" Object to simulate MachAr instance """
|
||
|
|
||
|
def __init__(self,
|
||
|
ftype,
|
||
|
**kwargs):
|
||
|
params = _MACHAR_PARAMS[ftype]
|
||
|
float_conv = lambda v: array([v], ftype)
|
||
|
float_to_float = lambda v : _fr1(float_conv(v))
|
||
|
self._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(kwargs.pop('eps'))
|
||
|
self.epsneg = float_to_float(kwargs.pop('epsneg'))
|
||
|
self.xmax = self.huge = float_to_float(kwargs.pop('huge'))
|
||
|
self.xmin = self.tiny = float_to_float(kwargs.pop('tiny'))
|
||
|
self.ibeta = params['itype'](kwargs.pop('ibeta'))
|
||
|
self.__dict__.update(kwargs)
|
||
|
self.precision = int(-log10(self.eps))
|
||
|
self.resolution = float_to_float(float_conv(10) ** (-self.precision))
|
||
|
|
||
|
# Properties below to delay need for float_to_str, and thus avoid circular
|
||
|
# imports during early numpy module loading.
|
||
|
# See: https://github.com/numpy/numpy/pull/8983#discussion_r115838683
|
||
|
|
||
|
@property
|
||
|
def _str_eps(self):
|
||
|
return self._float_to_str(self.eps)
|
||
|
|
||
|
@property
|
||
|
def _str_epsneg(self):
|
||
|
return self._float_to_str(self.epsneg)
|
||
|
|
||
|
@property
|
||
|
def _str_xmin(self):
|
||
|
return self._float_to_str(self.xmin)
|
||
|
|
||
|
@property
|
||
|
def _str_xmax(self):
|
||
|
return self._float_to_str(self.xmax)
|
||
|
|
||
|
@property
|
||
|
def _str_resolution(self):
|
||
|
return self._float_to_str(self.resolution)
|
||
|
|
||
|
|
||
|
# 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))
|
||
|
|
||
|
# 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)))
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
# 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)))
|
||
|
|
||
|
|
||
|
# 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 = {
|
||
|
b'\x9a\x99\x99\x99\x99\x99\xb9\xbf' : _float64_ma,
|
||
|
b'\xcd\xcc\xcc\xbd' : _float32_ma,
|
||
|
b'f\xae' : _float16_ma,
|
||
|
# float80, first 10 bytes containing actual storage
|
||
|
b'\xcd\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xfb\xbf' : _float80_ma,
|
||
|
# double double; low, high order (e.g. PPC 64)
|
||
|
b'\x9a\x99\x99\x99\x99\x99Y<\x9a\x99\x99\x99\x99\x99\xb9\xbf' :
|
||
|
_float_dd_ma,
|
||
|
# double double; high, low order (e.g. PPC 64 le)
|
||
|
b'\x9a\x99\x99\x99\x99\x99\xb9\xbf\x9a\x99\x99\x99\x99\x99Y<' :
|
||
|
_float_dd_ma,
|
||
|
# IEEE 754 128-bit binary float
|
||
|
b'\x9a\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\xfb\xbf' :
|
||
|
_float128_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 = _KNOWN_TYPES.get(key)
|
||
|
# Could be 80 bit == 10 byte extended precision, where last bytes can be
|
||
|
# random garbage. Try comparing first 10 bytes to pattern.
|
||
|
if ma_like is None and ftype == ntypes.longdouble:
|
||
|
ma_like = _KNOWN_TYPES.get(key[:10])
|
||
|
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(object):
|
||
|
"""
|
||
|
finfo(dtype)
|
||
|
|
||
|
Machine limits for floating point types.
|
||
|
|
||
|
Attributes
|
||
|
----------
|
||
|
bits : int
|
||
|
The number of bits occupied by the type.
|
||
|
eps : float
|
||
|
The smallest representable positive number such that
|
||
|
``1.0 + eps != 1.0``. Type of `eps` is an appropriate floating
|
||
|
point type.
|
||
|
epsneg : floating point number of the appropriate type
|
||
|
The smallest representable positive number such that
|
||
|
``1.0 - epsneg != 1.0``.
|
||
|
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 usable number. Type of `tiny` is an
|
||
|
appropriate floating point type.
|
||
|
|
||
|
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.
|
||
|
|
||
|
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.
|
||
|
|
||
|
"""
|
||
|
|
||
|
_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(object):
|
||
|
"""
|
||
|
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.")
|
||
|
|
||
|
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
|
||
|
|
||
|
min = property(min)
|
||
|
|
||
|
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
|
||
|
|
||
|
max = property(max)
|
||
|
|
||
|
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)
|
||
|
|