Source code for gfloat.types

# Copyright (c) 2024 Graphcore Ltd. All rights reserved.

from dataclasses import dataclass
from enum import Enum


[docs] class RoundMode(Enum): """ Enum for IEEE-754 rounding modes. Result r is obtained from input v depending on rounding mode as follows """ TowardZero = 1 #: :math:`\max \{ r ~ s.t. ~ |r| \le |v| \}` TowardNegative = 2 #: :math:`\max \{ r ~ s.t. ~ r \le v \}` TowardPositive = 3 #: :math:`\min \{ r ~ s.t. ~ r \ge v \}` TiesToEven = 4 #: Round to nearest, ties to even TiesToAway = 5 #: Round to nearest, ties away from zero
[docs] class FloatClass(Enum): """ Enum for the classification of a FloatValue. """ NORMAL = 1 #: A positive or negative normalized non-zero value SUBNORMAL = 2 #: A positive or negative subnormal value ZERO = 3 #: A positive or negative zero value INFINITE = 4 #: A positive or negative infinity (+/-Inf) NAN = 5 #: Not a Number (NaN)
[docs] @dataclass class FloatValue: """ A floating-point value decoded in great detail. """ code: int #: Integer code point #: Value. Assumed to be exactly round-trippable to python float. #: This is true for all <64bit formats known in 2023. fval: float exp: int #: Raw exponent without bias expval: int #: Exponent, bias subtracted significand: int #: Significand as an integer fsignificand: float #: Significand as a float in the range [0,2) signbit: int #: Sign bit: 1 => negative, 0 => positive fclass: FloatClass #: See FloatClass
[docs] @dataclass class FormatInfo: """ Class describing a floating-point format, parametrized by width, precision, and special value encoding rules. """ #: Short name for the format, e.g. binary32, bfloat16 name: str #: Number of bits in the format k: int #: Number of significand bits (including implicit leading bit) precision: int #: Largest exponent, emax, which shall equal floor(log_2(maxFinite)) emax: int #: Set if format encodes -0 at (sgn=1,exp=0,significand=0). #: If False, that encoding decodes to a NaN labelled NaN_0 has_nz: bool #: Set if format includes +/- Infinity. #: If set, the non-nan value with the highest encoding for each sign (s) #: is replaced by (s)Inf. has_infs: bool #: Number of NaNs that are encoded in the highest encodings for each sign num_high_nans: int #: Set if format encodes subnormals has_subnormals: bool #: Set if the format has a sign bit is_signed: bool #: Set if the format uses two's complement encoding for the significand is_twos_complement: bool #: ## Derived values @property def tSignificandBits(self) -> int: """The number of trailing significand bits, t""" return self.precision - 1 @property def expBits(self) -> int: """The number of exponent bits, w""" return self.k - self.precision + (0 if self.is_signed else 1) @property def signBits(self) -> int: """The number of sign bits, s""" return 1 if self.is_signed else 0 @property def expBias(self) -> int: """The exponent bias derived from (p,emax) This is the bias that should be applied so that :math:`floor(log_2(maxFinite)) = emax` """ # Calculate whether all of the all-bits-one-exponent values contain specials. # If so, emax will be obtained for exponent value 2^w-2, otherwise it is 2^w-1 t = self.tSignificandBits num_posinfs = 1 if self.has_infs else 0 all_bits_one_full = (self.num_high_nans + num_posinfs == 2**t) or ( self.expBits == 0 and self.has_infs ) # Compute exponent bias. exp_for_emax = 2**self.expBits - (2 if all_bits_one_full else 1) return exp_for_emax - self.emax # numpy finfo properties @property def bits(self) -> int: """ The number of bits occupied by the type. """ return self.k # @property # def dtype(self) -> np.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. # """ @property def eps(self) -> float: """ The difference between 1.0 and the 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. """ # TODO: Check if 1.0 is subnormal for any reasonable format, e.g. p3109(7)? return 2**self.machep @property def epsneg(self) -> float: """ The difference between 1.0 and the largest 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. """ return self.eps / 2 @property def iexp(self) -> int: """ The number of bits in the exponent portion of the floating point representation. """ return self.expBits @property def machep(self) -> int: """ The exponent that yields `eps`. """ return -self.tSignificandBits @property def max(self) -> float: """ The largest representable number. """ num_posinfs = 1 if self.has_infs else 0 num_non_finites = self.num_high_nans + num_posinfs if num_non_finites == 2**self.tSignificandBits: # All-bits-one exponent field is full, value is in the # binade below, so significand is 0xFFF..F isig = 2**self.tSignificandBits - 1 else: # All-bits-one exponent field is not full, value is in the # final binade, so significand is 0xFFF..F - num_non_finites isig = 2**self.tSignificandBits - 1 - num_non_finites if self.is_all_subnormal: return 2**self.emax * (isig * 2 ** (1 - self.tSignificandBits)) else: return 2**self.emax * (1.0 + isig * 2**-self.tSignificandBits) @property def maxexp(self) -> int: """ The smallest positive power of the base (2) that causes overflow. """ return self.emax + 1 @property def min(self) -> float: """ The smallest representable number, typically ``-max``. """ if self.is_signed: if not self.is_twos_complement: return -self.max else: assert not self.has_infs and self.num_high_nans == 0 and not self.has_nz return -(2 ** (self.emax + 1)) elif self.has_zero: return 0.0 else: return 2**-self.expBias @property def num_nans(self) -> int: """ The number of code points which decode to NaN """ if not self.is_signed: return self.num_high_nans # Signed if self.is_twos_complement: assert not self.has_infs and self.num_high_nans == 0 and not self.has_nz return 0 return (0 if self.has_nz else 1) + 2 * self.num_high_nans @property def code_of_nan(self) -> int: """ Return a codepoint for a NaN """ if self.num_high_nans > 0: return 2 ** (self.k) - 1 if not self.has_nz: return 2 ** (self.k - 1) raise ValueError(f"No NaN in {self}") @property def code_of_posinf(self) -> int: """ Return a codepoint for positive infinity """ if not self.has_infs: raise ValueError(f"No Inf in {self}") return 2 ** (self.k - 1) - 1 - self.num_high_nans @property def code_of_neginf(self) -> int: """ Return a codepoint for negative infinity """ if not self.has_infs: raise ValueError(f"No Inf in {self}") return 2**self.k - 1 - self.num_high_nans @property def code_of_zero(self) -> int: """ Return a codepoint for (non-negative) zero """ assert self.has_zero return 0 @property def has_zero(self) -> bool: """ Does the format have zero? This is false if the mantissa is 0 width and we don't have subnormals - essentially the mantissa is always decoded as 1. If we have subnormals, the only subnormal is zero, and the mantissa is always decoded as 0. """ return self.precision > 1 or self.has_subnormals @property def code_of_negzero(self) -> int: """ Return a codepoint for negative zero """ if not self.has_nz: raise ValueError(f"No negative zero in {self}") return 2 ** (self.k - 1) @property def code_of_max(self) -> int: """ Return a codepoint for fi.max """ return 2 ** (self.k - self.signBits) - self.num_high_nans - self.has_infs - 1 @property def code_of_min(self) -> int: """ Return a codepoint for fi.min """ if self.is_signed and not self.is_twos_complement: return 2**self.k - self.num_high_nans - self.has_infs - 1 elif self.is_signed and self.is_twos_complement: return 2 ** (self.k - 1) else: return 0 # codepoint of smallest value, whether 0 or 2^-expBias # @property # def minexp(self) -> int: # """ # The most negative power of the base (2) consistent with there # being no leading 0's in the mantissa. # """ # @property # def negep(self) -> int: # """ # The exponent that yields `epsneg`. # """ # @property # def nexp(self) -> int: # """ # The number of bits in the exponent including its sign and bias. # """ # @property # def nmant(self) -> int: # """ # The number of bits in the mantissa. # """ # @property # def precision(self) -> int: # """ # The approximate number of decimal digits to which this kind of # float is precise. # """ # @property # def resolution(self) -> float: # """ # The approximate decimal resolution of this type, i.e., # ``10**-precision``. # """ # @property # def tiny(self) -> float: # """ # An alias for `smallest_normal`, kept for backwards compatibility. # """ @property def smallest_normal(self) -> float: """ The smallest positive floating point number with 1 as leading bit in the significand following IEEE-754. """ if self.has_subnormals: return 2 ** (1 - self.expBias) elif self.has_zero: return 2**-self.expBias + 2 ** (-self.expBias - self.tSignificandBits) else: return 2**-self.expBias @property def smallest_subnormal(self) -> float: """ The smallest positive floating point number with 0 as leading bit in the significand following IEEE-754. """ assert self.has_subnormals, "not implemented" return 2 ** -(self.expBias + self.tSignificandBits - 1) @property def smallest(self) -> float: """ The smallest positive floating point number. """ if self.has_subnormals: return self.smallest_subnormal else: return self.smallest_normal @property def is_all_subnormal(self) -> bool: """ Are all encoded values subnormal? """ return (self.expBits == 0) and self.has_subnormals @property def __name__(self) -> str: return self.name def __str__(self) -> str: return f"{self.name}"