Author: varioustoxins Date: Tue Oct 31 12:31:13 2006 New Revision: 2716 URL: http://svn.gna.org/viewcvs/relax?rev=2716&view=rev Log: tidied code and added comments, code is now 4 space indented task 3621 https://gna.org/task/?3621 Modified: 1.2/float.py Modified: 1.2/float.py URL: http://svn.gna.org/viewcvs/relax/1.2/float.py?rev=2716&r1=2715&r2=2716&view=diff ============================================================================== --- 1.2/float.py (original) +++ 1.2/float.py Tue Oct 31 12:31:13 2006 @@ -30,24 +30,24 @@ ieee-74 uses special bit patterns to represent the following states or classes of ieee floating point numbers (ieee-class) - +-nan - not a number (e.g. 0.0/0.0) - inf - positive or negative infinity (1.0/0.0) - +-zero - zero maybe positive or negative under ieee-754 + +-nan - not a number (e.g. 0.0/0.0) + inf - positive or negative infinity (1.0/0.0) + +-zero - zero maybe positive or negative under ieee-754 this module provides functions for working with python floats and their special values, if they contain ieee-754 formatted values. Specifically - - pack and unpack a list of bytes representing an ieee-754 double to a python - float (takes care of little endian/big endian issues) - - get the sign bit of a python float - - check the ordering of python floats allowing for nans (nans cannot normally - be compared) - - check if a value is finite (as opposed to nan or inf) - - copy the sign of one float to another irrespective of if it's ieee-class - - check if a float is denormalised (and might be about to underflow) - - check the ieee-class of a python float (nan, pos-inf, neg-inf,pos-zero, - neg-zero,...) - - check that the current python float implmentations uses ieee-754 doubles - + - pack and unpack a list of bytes representing an ieee-754 double to a python + float (takes care of little endian/big endian issues) + - get the sign bit of a python float + - check the ordering of python floats allowing for nans (nans cannot normally + be compared) + - check if a value is finite (as opposed to nan or inf) + - copy the sign of one float to another irrespective of if it's ieee-class + - check if a float is denormalised (and might be about to underflow) + - check the ieee-class of a python float (nan, pos-inf, neg-inf,pos-zero, + neg-zero,...) + - check that the current python float implmentations uses ieee-754 doubles + It also provides constants containg specific bit patterns for nan and +-inf as these values cannot be generated from strings via the constructor float(x) with some compiler implementations (typically older microsoft windows compilers) @@ -56,43 +56,43 @@ in the draft python PEP 754 'IEEE 754 Floating Point Special Values' notes: - 1. binary data is docuemented as binary strings e.g. 0xF0 = 0b11110000 - 2. the module doesn't support all the functions recommened by ieee-754, - the following features are missing - a. control of exception and rounding modes - b. scalb (y, N) - c. logb (x) - d. nextafter(x,y) - e. next towards - 3. division by zero currently (python 2.5) raises excaption and the - resulting inf/nan cannot be propogated - 4. a second module ieeefloatcapabilities (currently incomplete) - provides tests of the capabilites of a floating point implementation - on a specific python platform - 5. development and conventions on byte order come from a little endian - (intel) platform - 6. to reduce overheads all functions that take python float arguments do - _no type_ conversion thus if other numeric types are passed the functions - will raise exceptions, (I am not sure this is the best behaviour however, - as python functions should be polymorphic...) - 7. in most cases conversion to c code for performance reasons would be trivial - + 1. binary data is docuemented as binary strings e.g. 0xF0 = 0b11110000 + 2. the module doesn't support all the functions recommened by ieee-754, + the following features are missing + a. control of exception and rounding modes + b. scalb (y, N) + c. logb (x) + d. nextafter(x,y) + e. next towards + 3. division by zero currently (python 2.5) raises excaption and the + resulting inf/nan cannot be propogated + 4. a second module ieeefloatcapabilities (currently incomplete) + provides tests of the capabilites of a floating point implementation + on a specific python platform + 5. development and conventions on byte order come from a little endian + (intel) platform + 6. to reduce overheads all functions that take python float arguments do + _no type_ conversion thus if other numeric types are passed the functions + will raise exceptions, (I am not sure this is the best behaviour however, + as python functions should be polymorphic...) + 7. in most cases conversion to c code for performance reasons would be trivial + ieee-754 double format: 63 sign bit 62-52 exponent (offset by 1023 value - field-1023 51-0 mantissa each bit n counts as 1/2^n, running from 1/2 which is the - most significant bit to 1/2^51, The 1/0 bit is defined by the - exponent field if it has any bits set if it has bits set then - precede the mantissa with a 1 (normalised otherwise procede it by - a 0 (denormalised) + most significant bit to 1/2^51, The 1/0 bit is defined by the + exponent field if it has any bits set if it has bits set then + precede the mantissa with a 1 (normalised otherwise procede it by + a 0 (denormalised) todo: - unit test suite - test under windows - test under a solaris sparc box (big endian) - add example ieee double - check byte/nibble atributions + unit test suite + test under windows + test under a solaris sparc box (big endian) + add example ieee double + check byte/nibble atributions ''' from struct import pack,unpack import sys @@ -149,35 +149,37 @@ ''' result = None + + # check finite if isFinite(float): - positive = isPositive(float) - denormalised = isDenormalised(float) - if denormalised(float): - if isZero(): - if positive: - result = CLASS_POS_ZERO - else: - result = CLASS_NEG_ZERO - else: - if positive: - result = CLASS_POS_DENORMAL - else: - result = CLASS_NEG_DENORMAL - else: - if positive: - result = CLASS_POS_NORMAL - else: - result = CLASS_NEG_NORMAL + # check and store is positive + positive = isPositive(float) + if isZero(): + if positive: + result = CLASS_POS_ZERO + else: + result = CLASS_NEG_ZERO + elif isDenormalised(float): + if positive: + result = CLASS_POS_DENORMAL + else: + result = CLASS_NEG_DENORMAL + else: + if positive: + result = CLASS_POS_NORMAL + else: + result = CLASS_NEG_NORMAL else: - if isNaN(float): - # we don't currently test the type of nan signalling vs quiet - result = CLASS_QUIET_NAN - elif isPosInf(float): - result = CLASS_POS_INF - elif isNegInf(float): - result = CLASS_NEG_INF - - + if isNaN(float): + # we don't currently test the type of nan signalling vs quiet + # so we always assume a quiet nan + result = CLASS_QUIET_NAN + elif isPosInf(float): + result = CLASS_POS_INF + elif isNegInf(float): + result = CLASS_NEG_INF + + def packBytesAsPyFloat(bytes): ''' pack 8 bytes into a python float @@ -191,12 +193,16 @@ returns -- a python float throws -- an Exception if bytes contains < 8 bytes - type of exception not determined - ''' - + type of exception not determined + ''' + # pack bytes into binary string doubleString=pack('8B',*bytes) + + #change byte order to little endian by reversing string if sys.byteorder == 'big': doubleString = doubleString[::-1] + + # unpack binary string to a python float return unpack('d',doubleString)[0] @@ -241,11 +247,15 @@ if not isinstance(obj,float): raise TypeError('the object recieved wasn\'t a float, type was: %s' % type(obj)) + # pack float into binary string packed =pack('d',obj) + + #change byte order to little endian by reversing string if sys.byteorder == 'big': - packed = packed[::-1] + packed = packed[::-1] + return packed - + def floatAsByteArray(obj): ''' unpack a python float as a list of 8 bytes @@ -259,8 +269,15 @@ throws -- throws an exception if obj is not composed of 8 bytes ''' - return list(unpack('8B',floatToBinaryString(obj))) - + #unpack bytes to a binary string (takes care of byte order) + binaryString = floatToBinaryString(obj) + + # convert the binary string to an array of 8 bytes + bytes = unpack('8B',binaryString) + + #convert bytes to a list for ease of editing + return list(bytes) + def getSignBit(obj): @@ -269,12 +286,16 @@ obj -- a python float object returns -- the floats sign bit, this has the value 1 if the float is - negative otherwise 0 (positive) + negative otherwise 0 (positive) throws -- throws a TypeError if the the input object isn't a python float - + ''' + + # unpack float to bytes unpacked = floatAsByteArray(obj) + + # grab last byte and check if sign bit is set return unpacked[7] & SIGNBIT def isPositive(obj): @@ -285,14 +306,14 @@ returns -- True if the float is positive otherwise False throws -- throws a TypeError if the the input object isn't a python float - + ''' - unpacked = floatAsByteArray(obj) + if getSignBit(obj): return False else: return True - + def isNegative(obj): ''' test if a a pyhton float 64 bit ieee-74 double is negative @@ -318,8 +339,12 @@ throws -- throws a TypeError if the the input objects aren't a python floats ''' + + # check to see if objects are nans nanTest1 = isNaN(obj1) nanTest2 = isNaN(obj2) + + # if either object is a nan we are unordered if nanTest1 or nanTest2: return True else: @@ -337,9 +362,10 @@ throws -- throws a TypeError if the the input object isn't a python float ''' + result = True if isNaN(obj): - result = False + result = False if isInf(obj): result = False @@ -356,26 +382,32 @@ toDouble -- the python float to copy the sign bit to throws -- throws a TypeError if toDouble isn't a python float or if - fromNumber can't be converted to a float - - ''' + fromNumber can't be converted to a float + + ''' + + #convert first number to a float so as to use facilities fromNumber = float(fromNumber) + + # check signs of numbers fromIsPositive = isPositive(fromNumber) toIsPositive = isPositive(toDouble) + + # convert the float to an array of 8 bytes toBytes = floatAsByteArray(toDouble) + if not toIsPositive and fromIsPositive: - toBytes[7] &= 0x7F + # unset the sign bit of the number + toBytes[7] &= EXPONENT_SIGN_MASK + elif toIsPositive and not fromIsPositive: - toBytes[7] = toBytes[7] + 128 + # set the sign bit + toBytes[7] = toBytes[7] + 0x80 + + #repack bytes to float return packBytesAsPyFloat(toBytes) - - - - - - - + def isDenormalised(obj): ''' check to see if a python float is denormalised @@ -388,15 +420,22 @@ result -- True if the number is denormalised throws -- throws a TypeError if toDouble isn't a python float or if - obj isn't a float - ''' + obj isn't a float + ''' + + result = True + # check to see if the exponent is all zeros (a denorm doesn't have a + # finite exponent) Note we ignore the sign of the float if not isExpAllZeros(obj): - return False - manBytes = getMantissaBytes(obj) - for byte in manBytes: - if byte > 0: - return True - return False + result = False + + # check to see if this is zero (which is in some ways a special + # class of denorm... but isn't counted in this case) + # if it isn't zero it must be a 'general' denorm + if isZero(obj): + result = False + + return result @@ -418,11 +457,15 @@ throws -- throws a TypeError if obj isn't a python float ''' - unpacked = floatToBinaryString(obj) - bytes = list(unpack('8B',unpacked)) + + # unpack float to bytes + bytes = floatAsByteArray(obj) + + # mask out overlap from exponent bytes[6] = bytes[6] & MANTISSA_NIBBLE_MASK - bytes=bytes[:7] - return bytes + + # remove the exponent bytes that can be removed + return bytes[:7] def getExponentBytes(obj): ''' get the 2 bytes that makeup the exponent of a float @@ -440,13 +483,17 @@ throws -- throws a TypeError if obj isn't a python float ''' - unpacked = floatToBinaryString(obj) - bytes = list(unpack('8B',unpacked)) + # unpack float to bytes + bytes = floatAsByteArray(obj) + + # mask out the ovberlap with the mantissa bytes[6] = bytes[6] & EXPONENT_NIBBLE_MASK + + # mask out the sign bit bytes[7] = bytes[7] & EXPONENT_SIGN_MASK - bytes=bytes[6:] - - return bytes + + # remove the mantissa bytes that can be removed + return bytes[6:] @@ -460,10 +507,16 @@ throws -- throws a TypeError if obj isn't a python float ''' + result = True + + # get the exponent as a byte array porperly masked expBytes = getExponentBytes(obj) + + # check to see if any of the bytes in the exponent are not zero if expBytes[0] > 0 or expBytes[1] > 0: - return False - return True + result = False + + return result def isMantissaAllZeros(obj): ''' check if the bits of the mantissa of a float is zero @@ -474,14 +527,19 @@ throws -- throws a TypeError if obj isn't a python float ''' + result = True + + # get the mantissa as a byte array properly masked mantissaBytes = getMantissaBytes(obj) - result = True + + # check if any of the mantissa bytes are greater than zero for byte in mantissaBytes: if byte != 0: result = False break + return result - + def isExpAllOnes(obj): ''' check if the bits of the exponent of a floatis all 1 bits @@ -491,10 +549,17 @@ throws -- throws a TypeError if obj isn't a python float ''' + + result = False + + # get the exponent as a byte array properly masked expBytes = getExponentBytes(obj) + + # check against masks to see if all the correct bits are set if expBytes[0] == EXPONENT_ALL_ONES_BYTE_0 and expBytes[1] == EXPONENT_ALL_ONES_BYTE_1: - return True - return False + result = True + + return result def isNaN(obj): ''' check to see if a python float is an ieee-754 double not a number (nan) @@ -505,17 +570,29 @@ throws -- throws a TypeError if obj isn't a python float ''' - + + # bad result for code checking + result = None + + # check to see if exponent is all ones (excluding sign bit) + # if exponent is not all ones this can't be a NaN if not isExpAllOnes(obj): - return False - manBytes = getMantissaBytes(obj) - manNan = False - for byte in manBytes[:6]: - if byte > 0: - manNan = True - if (manBytes[6] & MANTISSA_NIBBLE_MASK) > 0: - manNan = True - return manNan + result = False + else: + # get the mantissa as a byte array properly masked + manBytes = getMantissaBytes(obj) + + # check if any of the unmasked mantissa bytes are not zero + # to be a NaN the mantissa must be non zero + for byte in manBytes: + if byte > 0: + result = True + break + # todo NOT NEEDED, UNITTEST!!!! + # check to see if the mantissa nibble that overlaps with the + #if (manBytes[6] & MANTISSA_NIBBLE_MASK) > 0: + # result = True + return result def isInf(obj): ''' check to see if a python float is an infinity @@ -528,10 +605,20 @@ throws -- throws a TypeError if obj isn't a python float ''' + # bad result for code checking + result = None + + # check to see if exponent is all ones (excluding sign bit) + # if exponent is not all ones this can't be a Inf if not isExpAllOnes(obj): - return False + result = False + + # get the mantissa as a byte array properly masked manBytes = getMantissaBytes(obj) - for i,byte in enumerate(manBytes): + + for byte in manBytes: + #check if any of the unmasked mantissa bytes are zero + # to be a NaN the mantissa must be zero if byte > 0: return False return True @@ -559,9 +646,9 @@ ''' return isInf(obj) and not isPositive(obj) - - - - - - + + + + + +