Wiki Home

VFP Floating Point Data Type


Namespace: VFP
Floating Point Numbers - data types in VFP

There is no implementation (to my knowledge) of a 32 bit floating point (single precision) in VFP. Herewith is the official description of the format, followed by the VFP code to implement the 2 functions that convert to and from a 32 bit floating point for VFP.

The numeric range of 32 bit floating point numbers is restricted to 2 ** -126 to 2 ** 127 (2 ** 127 is approx 1.70E+38), whereas the Numeric in VFP7 uses 64 bits of memory and only has a precision of -0.9999999999E+19 to 0.999999999E+20.

The Double Field type in VFP7 is 64 bits, having a range between +/- 4.94E-324 to +/-8.98E+307 .

Anyway, these functions may offer a way of storing numbers of greater precision in a 32 bit / 4 bytes Char field in a VFP Table. Your comments welcome.

IEEE floating point format


IEEE 754-1990 standard for binary floating point arithmetic defined what is commonly referred to as "IEEE floating point". MIMOSA utilizes the 32-bit IEEE floating point format:

N = 1.F* 2^(E-127) (N = floating point number, F = fractional part in binary
notation, E = exponent in bias 127 representation)

In 32 bit IEEE format, 1 bit is allocated as the sign bit, the next 8 bits are allocated as the exponent field, and the last 23 bits are the fractional parts of the normalized number.

Sign Exponent Fraction
0 00000000 00000000000000000000000
Bit 31 [30 -- 23] [22 -- 0]

A sign bit of 0 indicates a positive number, and a 1 is negative. The exponent field is represented by "excess 127 notation". The 23 fraction bits actually represent 24 bits of precision, as a leading 1 in front of the decimal point is implied.

There are some exceptions.

E = 255; F = 0;
+/- infinity

E = 255; F != 0;
NaN, Not a number. Overflow, error.....

E = 0; F = 0;
0

E = 0; F != 0;
denormalized, tiny number, smaller than smallest allowed.

With exponent field 00000000 and 11111111 now reserved, the range is restricted to
2 ** -126 to 2 ** 127.

Here is the VFP code to make and decode 32bit floating point numbers.
To save results of large numbers, create a 4 char field in a table.

You will see some 'old favourites' from Anatoliy and others for the DWORD conversions and some of mine.
If you copy and paste the following to a PRG file, it should (:-) run.
Placed in the public domain

&& 0xBD 50 00 00 = 10111101 01010000 00000000 00000000
y = 2
set decimals to 18
m.i = SET("DECIMALS")
nRet = Bin2HexByte("10111100") &&BC or 188
nRet = Int2Bin(32000)

nRet = Int2Float(-0.05078125)
nRet = Float2Int(nRet)
nRet = Int2Float(nRet)
nRet = Float2Int(nRet)
nRet = Int2Float(nRet)
nRet = Float2Int(nRet)

nRet = Float2Int("BD500000")
nRet = Int2Float(nRet)

nRet = Bin2Int("00010001")
nRet = HexByte2Bin("03")
m.i = len(nRet)
nRet = HexByte2Bin("0F")
m.i = len(nRet)
nRet = Int2bin(128)
m.i = len(nRet)
nRet = Int2bin(0)
m.i = len(nRet)
m.x = 1

FUNCTION Bin2HexByte(cStr)
	local c1,c2,c3
	if vartype(cStr) != "C" OR len(cStr) != 8
		RETURN "00"
	endif
	c1 = Bin2Int(cStr) && Now a number between 0 and 255
	c2 = HexChar(int(c1 / 16))
	c3 = HexChar(c1 % 16)
	RETURN c2 + c3
ENDFUNC

FUNCTION HexChar(nNum) && a number between 0 and 15
	if nNum > 9
		RETURN chr(64 + nNum - 9)
	else
		RETURN alltrim(str(nNum))
	endif
ENDFUNC			

FUNCTION Int2Float(nNum)  && eg 27.4
	local cSign, nInt,nRem,i,c1,nPos,mVal,x,nLen
	c2 = ""
	if nNum < 0
		nNum = -nNum
		cSign = "1"
	else
		cSign = "0"
	endif	
	nInt = INT(nNum) && eg 27
	if nInt = 0
		nRem = nNum
	else	
		nRem = nNum % nInt && eg .4
	endif	
	c1 = Int2Bin(nInt)
	&& Now convert fraction if required
	if nRem > 0 
		nRem = int(nRem * 256)
		c2 = Int2bin(nRem)
		&& Now add together
		*c1 = c1 + "." + c2
	else
		c2 = "00000000"
	endif
	&& Now normalise an expression like 0001 1011.0110 0110
	nPos = at("1",c1) &&  = 4
	if nPos != 0
		nLen = len(c1)
		c1 = substr(c1,nPos+1) && Shift Mantissa 1 bit
		c3 = Int2Bin(127 + nLen - nPos)
	else	
		nLen = len(c2)
		nPos = at("1",c2)
		c1 = substr(c2,nPos+1)
		c2 = "00000000"
		c3 = Int2Bin(127  - nPos)
	endif	
	
	
	mVal = cSign + c3 + c1 + c2
	nPos = len(mVal)
	if nPos < 32 && Pad out to 32
		nPos = 32 - nPos 
		m.x = 0
		for m.i =  1 to nPos
			mVal = mVal + substr(c2,m.i - m.x,1)
			if m.i % 8 = 0
				m.x = m.x + 8
			endif
		endfor
	endif
	nPos = len(mVal)
	a1 = Bin2HexByte(substr(mVal,1,8))
	a2 = Bin2HexByte(substr(mVal,9,8))
	a3 = Bin2HexByte(substr(mVal,17,8))
	a4 = Bin2HexByte(substr(mVal,25,8))
	RETURN Bin2HexByte(substr(mVal,1,8)) + Bin2HexByte(substr(mVal,9,8)) +;
			Bin2HexByte(substr(mVal,17,8)) + Bin2HexByte(substr(mVal,25,8))
			
ENDFUNC	


FUNCTION Float2Int(dw)
	local cBin,cSign,cExp,cMant,nExp,nMant,cStr,mVal,nPos
	cBin = Dw2Bin(dw)
	_ClipText = cBin
	cSign = iif(substr(cBin,1,1) = "0",.t.,.f.)
	cExp = substr(cBin,2,8)
	cMant = substr(cBin,10)
	cStr = ""
	&& Convert the Exponent
	nExp = Bin2Int(cExp)- 127
	&& cMant is now 25 characters long with the decimal point at 2
	if cSign && Shift RIGHT for POSITIVE number
		cMant = "1" + substr(cMant,1,nExp) + "." + substr(cMant,nExp + 1)
		nPos = at(".",cMant)
		mVal = Bin2Int(substr(cMant,1,nPos - 1))
		cMant = substr(cMant,nPos + 1)
		nPos = rat("1",cMant)  
		if nPos > 0 && There is some decimal portion
			if nPos < 8 && Pad to 8
				nPos = 8
			endif
			cMant = substr(cMant,1,nPos)	
			mVal = mVal + Bin2Int(cMant)/2^nPos
		endif	
	else && SHIFT LEFT for NEGATIVE number
		if len(cMant) < (23 - nExp) && a FRACTION
			*cMant = "0." + replicate("0",(23 - nExp - 1 ) - len(cMant)) + "1" + cMant
			cMant = replicate("0",(23 - nExp - 1 ) - len(cMant)) + "1" + cMant
			nLen = len(cMant)
			for m.i = nLen to 1 STEP -1
				if substr(cMant,m.i,1) = "0" && Strip trailing zeros
					cMant = substr(cMant,1, m.i -1)
				else
					EXIT
				endif
			endfor
			&& Now convert to decimal
			mVal = Bin2Int(cMant)/2^len(cMant)
		
		endif			
	endif
	RETURN iif(cSign,mVal,-mVal)
ENDFUNC
FUNCTION Dw2Bin(dw)
	local b1,b2,b3,b4
	b1 = substr(dw,1,2)
	b2 = substr(dw,3,2)
	b3 = substr(dw,5,2)
	b4 = substr(dw,7,2)
	&& First convert to binary
	b1 = HexByte2Bin(b1)
	b2 = HexByte2Bin(b2)
	b3 = HexByte2Bin(b3)
	b4 =  HexByte2Bin(b4)
	RETURN b1 + b2 + b3 + b4
ENDFUNC

FUNCTION HexByte2Bin(cHexByte) && 0x00 to 0xFF
	local b1,b2,nVal
	b1 = substr(cHexByte,1,1)
	b2 = substr(cHexByte,2,1)
	b1 = iif(asc(b1) > 64, (9 + (asc(b1) - 64)) * 16, val(b1) * 16)
	b2 = iif(asc(b2) > 64, 9 + (asc(b2) - 64), val(b2))
	nVal = b1 + b2
	RETURN Int2Bin(nVal)

ENDFUNC

FUNCTION Int2Bin(nInt)
	local cStr,i,x,nRem
	cStr = ""
	if vartype(nInt) != "N" 
		RETURN ""
	endif	
	m.x = 1
	do while .t.
		if (2 ^ m.x) > nInt
			EXIT
		else
			m.x = m.x + 1
		endif
	enddo			
	m.x = m.x - 1
	for m.i = m.x to 1 STEP -1
		if int(nInt/(2 ^ m.i)) > 0
			cStr = cStr + "1"
			nInt = nInt - (2 ^ m.i)
				
		else
			cStr = cStr + "0"
		endif
	endfor
	cStr = cStr + iif(nInt > 0,"1","0")
	nLen = len(cStr)
	&& Pad to at least byte boundary
	nRem = nLen % 8
	if nRem > 0
		cStr = replicate("0",8 - nRem) + cStr
	endif	
	RETURN 	cStr	
ENDFUNC	

FUNCTION AscChar2Bin(xInt)
	local cStr,i
	cStr = ""
	if vartype(xInt) = "C"
		xInt = asc(xInt)
	endif	
	if vartype(xInt) != "N" OR nInt > 255 OR nInt < 0
		RETURN ""
	endif	
	for m.i = 7 to 1 STEP -1
		if int(xInt/(2 ^ m.i)) > 0
			cStr = cStr + "1"
			xInt = xInt - (2 ^ m.i)
		else
			cStr = cStr + "0"
		endif
	endfor
	nLen = len(cStr)
	RETURN iif(xInt > 0,cStr + "1",cStr + "0")		
ENDFUNC	
	
FUNCTION Bin2Int(cBin)
	local i,mNum,nLen
	mNum = 0
	nLen = len(cBin)
	for m.i = 1 to nLen 
		mNum = mNum + val(substr(cBin,m.i,1)) * (2 ^ (nLen - m.i))
	endfor
	RETURN mNum
ENDFUNC	

Contributors Peter Easson
Slightly shorter and faster Int2Bin implementation, processes negative input values as well:
FUNCTION Int2Bin(num)
	DO CASE
	CASE m.num = 0
		RETURN "0"
	CASE m.num < 0
		num = 0x100000000 + m.num
	ENDCASE
	LOCAL cResult, nIndex
	cResult = ""
	FOR nIndex=MIN(31, CEILING(LOG(m.num)/LOG(2))) TO 0 STEP -1
		cResult = m.cResult + IIF(BITTEST(m.num, m.nIndex), "1", "0")
	NEXT
RETURN cResult

Contributor: Anatoliy Mogylevets
My implementation of Int2Float is different from Peter's in a way it does not convert numeric values to strings (Int2Bin) to assemble 4-byte REAL data type number. That's why the code is shorter and works faster:
nReal = Int2Float(10.25)
? TRANS(nReal, "@0")

FUNCTION Int2Float(num)
#DEFINE REAL_BIAS 127
#DEFINE REAL_MANTISSA_SIZE 23
#DEFINE REAL_NEGATIVE 0x80000000
	LOCAL sgn, exponent, mantissa, fraction, bitptr

	DO CASE
	CASE num < 0
		sgn = REAL_NEGATIVE
		num = -num
	CASE num > 0
		sgn = 0
	OTHERWISE
		RETURN 0
	ENDCASE

	exponent = FLOOR(LOG(num)/LOG(2))
	mantissa = num - 2^exponent
	fraction = (mantissa % 1)
	mantissa = (mantissa - fraction) * 2^(REAL_MANTISSA_SIZE-exponent)

	IF fraction <> 0
		bitptr = REAL_MANTISSA_SIZE-exponent-1
		DO WHILE bitptr >= 0 AND fraction <> 0
			fraction=fraction * 2
			IF fraction >= 1
				mantissa = BITSET(mantissa, bitptr)
				fraction=fraction-1
			ENDIF
			bitptr=bitptr-1
		ENDDO
	ENDIF
	exponent = BITLSHIFT(exponent+REAL_BIAS, REAL_MANTISSA_SIZE)
RETURN BITOR(sgn, exponent, mantissa)

Can be even shorter, though less accurate. 2^ is used instead of BITLSHIFT, which makes possible shifting of the whole mantissa together with its fraction part:
#DEFINE REAL_BIAS 127
#DEFINE REAL_MANTISSA_SIZE 23
#DEFINE REAL_NEGATIVE 0x80000000
#DEFINE EXPONENT_MASK 0x7F800000
#DEFINE MANTISSA_MASK 0x007FFFFF

FUNCTION Int2Float(num)
	LOCAL sgn, exponent, mantissa

	DO CASE
	CASE num < 0
		sgn = REAL_NEGATIVE
		num = -num
	CASE num > 0
		sgn = 0
	OTHERWISE
		RETURN 0
	ENDCASE

	exponent = FLOOR(LOG(num)/LOG(2))
	mantissa = (num - 2^exponent)* 2^(REAL_MANTISSA_SIZE-exponent)
	exponent = BITLSHIFT(exponent+REAL_BIAS, REAL_MANTISSA_SIZE)
RETURN BITOR(sgn, exponent, mantissa)

And backwards:
FUNCTION Float2Int(num)
	LOCAL sgn, exponent, mantissa
	sgn = IIF(BITTEST(num,31), -1,1)
	exponent = BITRSHIFT(BITAND(num, EXPONENT_MASK), REAL_MANTISSA_SIZE) - REAL_BIAS
	mantissa = BITAND(num, MANTISSA_MASK) / 2^(REAL_MANTISSA_SIZE-m.exponent)
RETURN (2^m.exponent + m.mantissa) * m.sgn

Testing both functions:
num = Int2Float(123.45)
? num
? TRANSFORM(num, "@0")
bknum = Float2Int(num)
? bknum

Contributor: Anatoliy Mogylevets
Nice Work, Guys! I'd like to point out that if you use the "float" data type for passing to an outside program (through a file or through a DLL function call) you need to use Anatoliy's result format, which is byte-compatible with a real "float". Peter's output is as a character-hexadecimal representation of the 32bit value. For inclusion as a member of a C-style structure, neither will do, since it needs to be in a character-binary representation. Look for a modified version of Anatoliy's code in Api Structure Class soon!! -- wgcs
Take a look at how BINTOC function is used in VFP9. In gdiplus foundation class library class gpbase creates 32-bit float values using this function:
lparameters tw as number,th as number
...
return ;
	bintoc(m.tW,'F') + bintoc(m.tH,'F')

Anatoliy Mogylevets
Category Data Category Code Samples
( Topic last updated: 2004.08.07 10:46:41 PM )