549 lines
19 KiB
Python
549 lines
19 KiB
Python
"""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 = _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'])
|
|
|
|
|
|
@set_module('numpy')
|
|
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 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.
|
|
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.
|
|
|
|
"""
|
|
|
|
_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)
|
|
|
|
|
|
@set_module('numpy')
|
|
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,))
|
|
|
|
@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)
|
|
|