Hello Charles,
I would like to create a function that recovers the value stored in a PowerBASIC DOUBLE.
http://www.rit.edu/~meseec/eecc250-w.../IEEE-754.html
The first step for me would be to code this in BASIC
Then I would like to develop a function for converting a DOUBLE to text.
Lastly perhaps create and ASM function to replace FORMAT$()
The DOUBLE is of interest here because all floating point numbers are returned by SQL lite as DOUBLE's.
I am not having success in accuratly recovering them.
Hi Mike,
I am puzzled because I thought the bit format of a DOUBLE, SINGLE etc is a universal standard. Can you explain what sort of errors you are getting?
Are they rounding errors or do they look like type conversion problems?
PS: couldn't get the link to work but this describes the standard well:
http://en.wikipedia.org/wiki/IEEE_754
Yes, these are rounding errors.
After beating this around on the PB forum, I now realize that what i need is a BCD floating point variable, as the ieee binary spec is unable to represent some floats exactly.
As I think about this I realize this might mean writing a class of functions to handle addition, subtraction and multiplication and division. How insane an idea is that?
As a former programmer of business applications, I used BCD in DOS times. Today I will use the DECIMAL data type. Here is my definition of the data type and the declarations of the available API functions:
TYPE DECIMAL
wReserved AS WORD
scale AS BYTE ' // The number of decimal places for the number.
' // Valid values are from 0 to 28. So 12.345 is represented
' // as 12345 with a scale of 3.
sign AS BYTE ' // 0 for positive numbers or %DECIMAL_NEG (&H80) for
' // negative numbers. So -1 is represented as
' // 1 with the %DECIMAL_NEG bit set.
Hi32 AS DWORD ' // The high 32 bits of your number
Lo64 AS QUAD ' // The low 64 bits of your number. This is an _int64 (QUAD in PB)
END TYPE
%DECIMAL_NEG = &H80?
DECLARE FUNCTION VarUI1FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarUI1FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF BYTE _ ' [out] BYTE * pbOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI2FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarI2FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF INTEGER _ ' [out] SHORT * psOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI4FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarI4FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF LONG _ ' [out] LONG * plOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI8FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarI8FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF QUAD _ ' [out] LONG64 FAR* pi64Out
) AS LONG ' HRESULT
DECLARE FUNCTION VarR4FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarR4FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF SINGLE _ ' [out] FLOAT *pfltOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarR8FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarR8FromDec" ( _
BYREF ANY _ ' [in] DECIMAL pdecIn
, BYREF DOUBLE _ ' [out] double pdblOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDateFromDec _
LIB "OLEAUT32.DLL" ALIAS "VarDateFromDec" ( _
BYREF ANY _ ' DECIMAL *pdecIn
, BYREF DOUBLE _ ' DATE *pdateOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarCyFromDec _
LIB "OLEAUT32.DLL" ALIAS "VarCyFromDec" ( _
BYREF ANY _ ' [in] DECIMAL pdecIn
, BYREF CUR _ ' [out] CY * pcyOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarBstrFromDec _
LIB "OLEAUT32.DLL" ALIAS "VarBstrFromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYVAL DWORD _ ' [in] LCID lcid
, BYVAL DWORD _ ' [in] ULONG dwFlags
, BYREF STRING _ ' [out] BSTR *pbstrOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarBoolFromDec _
LIB "OLEAUT32.DLL" ALIAS "VarBoolFromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF INTEGER _ ' [out] VARIANT_BOOL * pboolOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI1FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarI1FromDec" ( _
BYREF ANY _ ' __in DECIMAL *pdecIn
, BYREF BYTE _ ' __out CHAR *pcOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarUI2FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarUI2FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF WORD _ ' [out] USHORT *puiOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarUI4FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarUI4FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF DWORD _ ' [out] ULONG *pulOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarUI8FromDec _
LIB "OLEAUT32.DLL" ALIAS "VarUI8FromDec" ( _
BYREF ANY _ ' [in] DECIMAL *pdecIn
, BYREF QUAD _ ' [out] ULONG64 FAR* pi64Out
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromUI1 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromUI1" ( _
BYVAL BYTE _ ' [in] BYTE bIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromI2 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromI2" ( _
BYVAL INTEGER _ ' [in] SHORT uiIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromI4 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromI4" ( _
BYVAL LONG _ ' [in] LONG lIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromI8 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromI8" ( _
BYVAL QUAD _ ' [in] i64In
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromR4 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromR4" ( _
BYVAL SINGLE _ ' [in] FLOAT fltIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromR8 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromR8" ( _
BYVAL DOUBLE _ ' [in] double dblIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromDate _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromDate" ( _
BYVAL DOUBLE _ ' [in] DATE dateIn
, BYREF ANY _ ' [out] DECIMAL * pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromCy _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromCy" ( _
BYVAL CUR _ ' [in] CURRENCY cyIn
, BYREF ANY _ ' [out] DECIMAL pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromStr _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromStr" ( _
BYVAL DWORD _ ' OLECHAR *strIn
, BYVAL DWORD _ ' LCID lcid
, BYVAL DWORD _ ' ULONG dwFlags
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromDisp _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromDisp" ( _
BYVAL IDispatch _ ' IDispatch *pdispIn
, BYVAL DWORD _ ' LCID lcid
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromBool _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromBool" ( _
BYVAL INTEGER _ ' VARIANT_BOOL boolIn
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromI1 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromI1" ( _
BYVAL BYTE _ ' CHAR cIn
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromUI2 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromUI2" ( _
BYVAL WORD _ ' USHORT uiIn
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromUI4 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromUI4" ( _
BYVAL DWORD _ ' ULONG ulIn
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFromUI8 _
LIB "OLEAUT32.DLL" ALIAS "VarDecFromUI8" ( _
BYVAL QUAD _ ' ULONG64 ui64In
, BYREF ANY _ ' DECIMAL *pdecOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI4FromI8 _
LIB "OLEAUT32.DLL" ALIAS "VarI4FromI8" ( _
BYVAL QUAD _ ' LONG64 i64In
, BYREF LONG _ ' LONG *plOut
) AS LONG ' HRESULT
DECLARE FUNCTION VarI4FromUI8 _
LIB "OLEAUT32.DLL" ALIAS "VarI4FromUI8" ( _
BYVAL QUAD _ ' LONG64 i64In
, BYREF LONG _ ' LONG *plOut
) AS LONG ' HRESULT
'// Decimal math
'//
DECLARE FUNCTION VarDecAdd _
LIB "OLEAUT32.DLL" ALIAS "VarDecAdd" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecLeft
, BYREF ANY _ ' [in] LPDECIMAL pdecRight
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecDiv _
LIB "OLEAUT32.DLL" ALIAS "VarDecDiv" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecLeft
, BYREF ANY _ ' [in] LPDECIMAL pdecRight
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecMul _
LIB "OLEAUT32.DLL" ALIAS "VarDecMul" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecLeft
, BYREF ANY _ ' [in] LPDECIMAL pdecRight
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecSub _
LIB "OLEAUT32.DLL" ALIAS "VarDecSub" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecLeft
, BYREF ANY _ ' [in] LPDECIMAL pdecRight
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecAbs _
LIB "OLEAUT32.DLL" ALIAS "VarDecAbs" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecFix _
LIB "OLEAUT32.DLL" ALIAS "VarDecFix" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecInt _
LIB "OLEAUT32.DLL" ALIAS "VarDecInt" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecNeg _
LIB "OLEAUT32.DLL" ALIAS "VarDecNeg" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecRound _
LIB "OLEAUT32.DLL" ALIAS "VarDecRound" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYVAL LONG _ ' [in] int cDecimals
, BYREF ANY _ ' [out] LPDECIMAL pdecResult
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecCmp _
LIB "OLEAUT32.DLL" ALIAS "VarDecCmp" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecLeft
, BYREF ANY _ ' [in] LPDECIMAL pdecRight
) AS LONG ' HRESULT
DECLARE FUNCTION VarDecCmpR8 _
LIB "OLEAUT32.DLL" ALIAS "VarDecCmpR8" ( _
BYREF ANY _ ' [in] LPDECIMAL pdecIn
, BYVAL DOUBLE _ ' [in] double dblRight
) AS LONG ' HRESULT
You could have a pair of functions to interconvert an Extended Precision number to a 20 byte hexadecimal coding which could be stored in a Sqlite string field.
Take the binary nybbles and add 65 so that the code looks something like this: ABDPFEGAHIKEFDNOMAEJ with characters between A and P.
To able to sort on such a field correctly, the characters must be stored in reverse order so that the most significant is the leftmost.
This encoding/decoding can be done efficiently in assembler. I can come up with the code if you think this is a practical way to go.
If Extended Precision proves to be insufficient then yes, its going to be customised arithmetic functions.
Wow. This gives me a lot to work with. Thank you guys.
- Corrected exponential ranges to those specified in IEEE-754.
- Correction of decimal exponent field size.
- Made correction so that decimal significand is displayed even when
its value is 0.
- decBinVal array sizes corrected for small values (those near 1.0*2**-126
for 32-bit or 1.0*2**-1022 for 64-bit).
- Corrected ieee32.Convert2Dec() to ieee64.Convert2Dec() in the ieee64
section.
- Added support for zero and unnormalized numbers (those less than
1.0*2**-126 for 32-bit or 1.0*2**-1022 for 64-bit).
- Modified significand input field to hold the notationally largest range
input value (with the most significant digits, exponent, and signs),
-4.94065645841246544E-324 .
- Use only 1 set of intBinVal and decBinVal arrays so that 32-bit results
will be marked invalid when 64-bit values greater than 3.40282347E38
are entered.
- Joined arrays intBinVal and decBinVal into one array, BinVal, in order
to ease the manipulations involved in rounding.
- Added IEEE-754 round-to-nearest value rounding mode.
- Added input echo field which displays how the host machine sees the
input value (round-off, number of significant digits, max and min
exponentials, etc.).
- For both precisions, added an echo field which displays the input value
accurate to the number of bits in that precision's significand.
- Added action so that when an input value overflows a precision, set that
precision's outputs to the precision's Infinity values instead of having
the bits be replaced by "#"s.
- For both precisions, added a status field indicating from high to low:
overflow, normal, unnormalized, underflow, and normal for zero.
- Run Convert2Bin() only once per precision (not for each substring).
- Added the ability to round or not to round by providing 2 activation
buttons labeled as such.
- Reduced exponent input field to hold up to the notationally largest
exponent value (with the most significant digits and sign), -324, unless
someone wants to oddly decimalize (add fractional/decimal parts to) the
exponent.
- Replaced calls to round() with tests and corrections for rounding up to
calls to floor() which always rounds down.
1998/09/28 to 1998/09/30
- Made BinVal external to Convert2Bin (and therefore local to function ieee)
as this.BinVal as is required in IEEE-754hex64.html and IEEE-754hex32.html
so that Unix 'diff' has some chance at comparing this file with
IEEE-754hex64.html or IEEE-754hex32.html .
- General clean-ups.
- Removed the optional "Exponent:" input field due to its problem of causing
the entire input to underflow to zero when its value is -324, the problems
encountered when trying to correct this deficiency programatically, and
its superfluousness due to the ability of entering scientific notation
(4.94065645841246544E-324) into the "Significand:" field alone.
- Renamed the "Significand:" input field to "Decimal Floating-Point:".
1998/10/06 to 1998/10/07
- Added removal of input leading and trailing blanks by adding RemoveBlanks
routine.
- Added converting all floating-point inputs into canonical form (scientific
notation, +x.xxE+xxxx form) with constant exponential width and plus sign
usage by adding the Canonical routine, in order to simplify the string
number comparisons involved in the 1.7976931348623157E+308 overflow check.
- Found and corrected bug in both binary outputs created by some unknown
JavaScript scoping problem for the symbol 'sOut' by introduction of the
Canonical routine (never in a released version).
- Floating-point input is now hand parsed and then its magnitude is compared
against +1.7976931348623157E+00308 in the OvfCheck routine. If the
magnitude is greater than this value (compared as a string), set all
outputs to the appropriate Infinity values. The JavaScript implementation
of IEEE-754 64-bit precision clips such values at 1.7976931348623157E+308.
- Greatly improved the efficiency of the Convert2Hex routine.
1998/10/20
- Allow power of 10 indicator in numStrClipOff to be "E" as well as "e" in
case not all browsers use "e".
- Reordered 'numerals' in OvfCheck so that their index (value) is the same
as that which the numeral represents.
1998/10/23
- With hand parsed floating-point input, compare its magnitude against
+2.4703282292062328E-00324 in the UndfCheck routine. If the magnitude is
less than this value (compared as a string), set 'this.StatCond' to
"underflow". The JavaScript implementation of IEEE-754 64-bit precision
underflows such values to zero.
- The above required all settings of 'this.StatCond' to "normal" to have to
be removed, "normal" rather than "error" is now the default.
- With hand parsed input, set the output sign bit from its minus sign
character before the input is turned into a numeric. This supports input
of negative zeros, "-0", and those values which underflow to it.
1998/10/28
- The central testing section of OvfCheck and UndfCheck were joined together
as the single routine A_gt_B.
- The translation which moves an input's base 10 exponential (the sign and
value to the right of the "E") to its left end as its sign and most
significant digits is now performed by the MostSigOrder routine.
- Due to tests now involving negative exponentials in A_gt_B (via UndfCheck),
a bias of 50,000 is added to all exponentials, when moved by MostSigOrder,
in order to simplify numeric compares performed as iterative comparisons of
individual digits in strings.
1998/10/29
- Floating-point input is now hand parsed, manipulated, and placed into the
this.BinVal array only once by introduction of the Dec2Bin routine.
As a result, the Convert2Bin routine now used is quite similar to that of
IEEE-754hex64.html and IEEE-754hex32.html .
32-bit:
3.4028234663852886E+38 down to (1.9999998 * 2**127)
1.1754942807573643E-38 normalized (1.0 * 2**-126)
1.4012984643248170E-45 unnormalized (1.0 * 2**-149)
(7.0064923216240862E-46) " (1.0 * 2**-150)
64-bit:
1.7976931348623157E+308 down to (1.9999999999999996 * 2**1023)
2.2250738585072016E-308 normalized (1.0 * 2**-1022)
4.9406564584124654E-324 unnormalized (1.0 * 2**-1074)
(2.4703282292062328E-324) " (1.0 * 2**-1075)
Float Values (b = bias)
Sign Exponent (e) Mantissa (m) Value
----------------------------------------------------------
11..11 Quiet
1 11..11 : -NaN
10..01
----------------------------------------------------------
1 11..11 10..00 Indeterminate
----------------------------------------------------------
01..11 Signaling
1 11..11 : -NaN
00..01
----------------------------------------------------------
1 11..11 00..00 -Infinity
----------------------------------------------------------
11..10 11..11 Negative Normalized Real
1 : : -1.m * 2**(e-b)
00..01 00..00
----------------------------------------------------------
11..11 Negative Denormalized Real
1 00..00 : -0.m * 2**(-b+1)
00..01
----------------------------------------------------------
1 00..00 00..00 -0
----------------------------------------------------------
0 00..00 00..00 +0
----------------------------------------------------------
00..01 Positive Denormalized Real
0 00..00 : 0.m * 2**(-b+1)
11..11
----------------------------------------------------------
00..01 00..00 Positive Normalized Real
0 : : 1.m * 2**(e-b)
11..10 11..11
----------------------------------------------------------
0 11..11 00..00 +Infinity
----------------------------------------------------------
00..01 Signaling
0 11..11 : +NaN
01..11
----------------------------------------------------------
10..00 Quiet
0 11..11 : +NaN
11..11
----------------------------------------------------------
In the following, 's' stands for the sign bit, 'e' stands for the exponent,
and 'm' stands for the fractional part of the mantissa. The symbol 'x' stands
for a "don't care" bit (either a 0 or 1).
For single-precision (32-bit) floating point numbers:
Type s (1 bit) e (8 bits) m (23 bits)
---- --------- ---------- -----------
signaling NaN x 255 (max) .0xxxxx---x
(with at least
one 1 bit)
quiet NaN x 255 (max) .1xxxxx---x
Indeterminate 1 255 (max) .100000---0
negative infinity 1 255 (max) .000000---0
positive infinity 0 255 (max) .000000---0
negative zero 1 0 .000000---0
positive zero 0 0 .000000---0
For double-precision (64-bit) floating point numbers:
Type s (1 bit) e (11 bits) m (52 bits)
---- --------- ----------- -----------
signaling NaN x 2047 (max) .0xxxxx---x
(with at least
one 1 bit)
quiet NaN x 2047 (max) .1xxxxx---x
Indeterminate 1 2047 (max) .100000---0
negative infinity 1 2047 (max) .000000---0
positive infinity 0 2047 (max) .000000---0
negative zero 1 0 .000000---0
positive zero 0 0 .000000---0
This code is only tested for 64bit floating point variables.
The conversion to hex is not exactly correct
Mike, does extended precision arithmetic meet your needs? This is the native format of the FPU, so using variables of this type ensures you can work without losses by intermediate variables of lesser precision.
One point to note is that PB requires the extended form of STR$ to convert them properly into text: STR$(n,18).
Well I could use extended precision or even in fact a double precision as we all have for years, but at this point I want to explore alternatives to the ieee format.
Next I think I will do some speed tests with Jose's Decimal data type.
There is a Blob Data type in SqLite. You could use it to store the binary of any arbitrary number format. It all depends on what properties this data type has. Whether you can sort or select ranges if you need to.
If blob data is inadequate then we can pursue the text encoding idea.
Yes the blob is great for this. The problem is with the queries. I don't know if they are going to want to use the values in a query. If they do I will have to write a UDF to convert each value first. It would be better to have this done as part of the data prep, but that means using one of the underlying datatypes QUAD or DOUBLE. The other thing to consider is some clever use of text that would sort etc correctly, but that is can of worms I would rather not open.
Apart from that, how these numbers are handled once returned is another matter. I still don't know what the range of data is. There is no sample data and great potential for large values.
Anyway, for now I am playing with the DECIMAL.
#COMPILE EXE
#DIM ALL
#INCLUDE "Decimal.inc"
GLOBAL hDbg AS LONG
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count Charles E V Pegge
'---------------------------'
' ' approx because it is not a serialised instruction
' ' it may execute before or after other instructions
' ' in the pipeline.
! mov ebx,tick ' var address where count is to be stored.
! db &h0f,&h31 ' RDTSC read time-stamp counter into edx:eax hi lo.
! mov [ebx],eax ' save low order 4 bytes.
! mov [ebx+4],edx ' save high order 4 bytes.
'---------------------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
FUNCTION PBMAIN
LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG
LOCAL cBeg, cEnd AS QUAD ' for time stamp, measuring cpu clock cycles
LOCAL d AS DOUBLE
LOCAL s, sTemp AS STRING
LOCAL Dec1, Dec2, Dec3 AS Decimal
LOCAL pDec1, pDec2, pDec3 AS Decimal PTR
hDbg = FREEFILE '
OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)
pDec1 = VARPTR(Dec1)
pDec2 = VARPTR(Dec2)
pDec3 = VARPTR(Dec3)
nLoops = 4 ' 10000000
d = 523.34#
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
d = d * 3
d = d / 3
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "VAL =" + STR$(d) + ", Clock Cycles="+STR$( (cEnd-cBeg)\nLoops ) + $CRLF + $CRLF
'=======================
LONGvar = 52334
RetVal = VarDecFromI4(LONGvar, pDec1) ' store 52334 (Integer) in Dec1
LONGvar = 100
RetVal = VarDecFromI4(LONGvar, pDec2) ' store 100 (Integer) in Dec3
RetVal = VarDecDiv(pDec1, pDec2, pDec3) ' Divide them to get the float "523.34"
RetVal = VarR8FromDec(pDec3, d)
s = s + "523.34 =" + STR$(d) + $CRLF
Dec1 = Dec3 ' xfer to Dec 1
LONGvar = 3
RetVal = VarDecFromI4(LONGvar, pDec2) ' prepare Dec2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
RetVal = VarDecMul(pDec1, pDec2, pDec3) ' 523.34*3
RetVal = VarDecDiv(pDec1, pDec2, pDec3) ' 523.34/3
NEXT '
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
RetVal = VarR8FromDec(pDec3, d)
s = s + "Result =" + STR$(d) + ", Clock Cycles="+STR$( (cEnd-cBeg)\nLoops ) + $CRLF + $CRLF
'=======================
' BYREF ANY _ ' [in] DECIMAL *pdecIn
' , BYVAL DWORD _ ' [in] LCID lcid
' , BYVAL DWORD _ ' [in] ULONG dwFlags
' , BYREF STRING _ ' [out] BSTR *pbstrOut
' ) AS LONG ' HRESULT
sTemp = " " ' guess
CALL VarBstrFromDec(pDec3, LEN(sTemp), BYVAL 0, sTemp )
s = s + "VarBstrFromDec =" + sTemp
PRINT #hDbg, s
MSGBOX s,64,"All Done" : EXIT FUNCTION
CLOSE hDbg
END FUNCTION
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
I cant figure out why the values are all wrong
Quote
I cant figure out why the values are all wrong
Lack of proper understanding about parameter passing conventions? What are you doing using VARPTR to pass the address of a DECIMAL structure if the parameter has been declared as BYREF ANY? This only will work if you use BYVAL; otherwise, you are passing the address of an address. Also, as VarBstrFromDec returns an UNICODE string, you have to use ACODE$ with your message box.
An small example:
FUNCTION PBMAIN
LOCAL hr AS LONG
LOCAL Dec1, Dec2, Dec3 AS DECIMAL
LOCAL strVal AS STRING
hr = VarDecFromI4(52334, Dec1) ' store 52334 (Integer) in Dec1
hr = VarDecFromI4(100, Dec2) ' store 100 (Integer) in Dec3
hr = VarDecDiv(Dec1, Dec2, Dec3) ' Divide them to get the float "523.34"
VarBstrFromDec(Dec3, 0, 0, strVal)
MSGBOX ACODE$(strVal)
END FUNCTION
And the lcid parameter is not the length of the string, but the locale identifier (0 for default locale). For a list of locale identifiers, see:
http://msdn2.microsoft.com/en-us/library/ms221219.aspx
Re: Extended precision numbers:
whole numbers are accurate to 18 digits but this deteriorates with fractional numbers down to around 15 digits, as I have discovered. It will be interesting to compare this with José's Decimal code.
The loss of resolution occurs when decimalising the numbers.
Re: Text encoding numbers:
Before reversing the order of hexadigits and encoding them as text, the sign bit must be inverted, when converting extended precision numbers. Exponent and mantissa are in the correct order of priority so there is no need to treat them separately.
Signed integers are a little more complicated: they must be converted from twos complement format to an absolute value and an inverted sign bit, similar to the above.
But once this is done, the numbers will be ordered correctly, when the database treats them as text,
obeying all the rules governing text fields.
I have not spotted any other worms so far.
QuoteWhat are you doing using VARPTR to pass the address of a DECIMAL structure if the parameter has been declared as BYREF ANY?
Ah... a casualty of developing on four fronts at the same time... one of which is all pointers. The last few weeks I see pointers when I close my eyes. Thx Jose.
Well timing results indicate DECIMAL operations to be about 5x slower than DOUBLE operations for mutiplication and division. Not as bad as the order of magnitude described in some threads I read in the last week.
#COMPILE EXE
#DIM ALL
#INCLUDE "Decimal.inc"
GLOBAL hDbg AS LONG
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count Charles E V Pegge
'---------------------------'
' ' approx because it is not a serialised instruction
' ' it may execute before or after other instructions
' ' in the pipeline.
! mov ebx,tick ' var address where count is to be stored.
! db &h0f,&h31 ' RDTSC read time-stamp counter into edx:eax hi lo.
! mov [ebx],eax ' save low order 4 bytes.
! mov [ebx+4],edx ' save high order 4 bytes.
'---------------------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
FUNCTION PBMAIN
LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG
LOCAL cBeg, cEnd AS QUAD ' for time stamp, measuring cpu clock cycles
LOCAL d AS DOUBLE
LOCAL s, sTemp AS STRING
LOCAL Dec1, Dec2, Dec3 AS Decimal
hDbg = FREEFILE '
OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)
nLoops = 10000000
d = 523.34#
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
d = d * 3
d = d / 3
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "VAL =" + STR$(d) + ", Clock Cycles="+STR$( (cEnd-cBeg)\nLoops ) + $CRLF + $CRLF
'=======================
LONGvar = 52334
RetVal = VarDecFromI4(LONGvar, Dec1) ' store 52334 (Integer) in Dec1
LONGvar = 100
RetVal = VarDecFromI4(LONGvar, Dec2) ' store 100 (Integer) in Dec3
RetVal = VarDecDiv(Dec1, Dec2, Dec3) ' Divide them to get the float "523.34"
Dec1 = Dec3 ' xfer to Dec 1
LONGvar = 3
RetVal = VarDecFromI4(LONGvar, Dec2) ' prepare Dec2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
RetVal = VarDecMul(Dec1, Dec2, Dec3) ' 523.34*3
RetVal = VarDecDiv(Dec1, Dec2, Dec3) ' 523.34/3
NEXT '
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
CALL VarBstrFromDec(Dec3, 0, 0, sTemp )
s = s + "Result =" + ACODE$(sTemp) + ", Clock Cycles="+STR$( (cEnd-cBeg)\nLoops ) + $CRLF + $CRLF
'=======================
PRINT #hDbg, s
MSGBOX s,64,"All Done" : EXIT FUNCTION
CLOSE hDbg
END FUNCTION
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Charles, how do you suggest I make the comparison
Well, your example shows 29 digit accuracy (26 decimal places) if I counted correctly. That should satisfy most cosmologists. But 15 digits should be good enough for most applications - even in Wall St :)
It's a direct trade-off between accuracy and performance, but if you can avoid handling fractions directly then 18 digits are possible with Extended Precision.
Updated: 10 Jan 2008: to prevent encoder from reversing the sign of the original number.
Here are the Extended Number Text encoding/decoding functions as envisaged. They are fairly close to max efficiency at around 100 clocks each (theoretically, I have not timed them). Since they operate on the binary number directly, there are no precision losses.
In a database alpha sorting on the text codes will put the values into ascending order. Negative numbers are handled correctly in this regard. Range selection should also be possible by using encoded range values.
' Extended Number Text encoder/decoder functions
' Charles E V Pegge
' 10 Jan 2008
#COMPILE EXE
#DIM ALL
SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! ADD esi,9 ' offset to end of number
'----------------'
! mov ecx,5 ' 5 loops to process 2 bytes and produce 4 bytes
' in each cycle
'----------------'
nex4: '
! mov dl,[esi] ' load digit (we go from msb to lsb)
! mov dh,dl ' prepare to split the nybbles
! shr dl,4 ' most significant nybble in dl
! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
! mov eax,edx ' move the result to eax
! dec esi ' decrement number digit pointer
'----------------'
! mov dl,[esi] ' get the next 2 nybbles
! mov dh,dl ' split them
! shr dl,4 ' dl to contain the highest
! AND edx,&h0f0f ' mask off unwanted bits
! shl edx,16 ' move along 2 bytes in the string
! ADD eax,edx ' combine with the previous 2 bytes
! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
! mov [edi],eax ' store the 4 bytes into the string
! ADD edi,4 ' advance the string pointer for next.
'----------------'
! dec esi ' decrement the number pointer
! dec ecx ' decrement the cycle counter
! jg nex4 ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
! xor byte ptr [edi-20],8 ' invert sign bit
'----------------'
END SUB
SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! add esi,9 ' offset to last byte of number
'----------------'
! mov ecx,5 '
nex4: '
! mov eax,[edi] '
! add edi,4 ' advance pointer for next 4 chars
! sub eax,&h41414141 ' subtract ascii offsets
'----------------'
! mov dh,al ' get higher nybble
! shl dh,4 ' mov it up *16
! mov dl,ah ' hold it in dl
! and dl,&hf ' mask off unwanded bits
! add dl,dh ' add higher to lower nybble
! mov [esi],dl ' store the recomposed byte
! dec esi ' work backwards towards lsb of number
! shr eax,16 ' shift right to get the next 2 chars
'----------------'
! mov dh,al ' store as higher nybble
! shl dh,4 ' shift into position
! mov dl,ah ' move to dl
! and dl,&hf ' mask unwanted bits
! add dl,dh ' add higher to lower
! mov [esi],dl ' store the recomposed byte to the number
! dec esi ' move left for the next lower byte
! shr eax,16 ' shift for the next 2 chars
'----------------'
! dec ecx '
! jg nex4 '
! xor byte ptr [esi+10], &h80 ' invert sign bit
'----------------'
END SUB
FUNCTION PBMAIN () AS LONG
LOCAL n,m,v AS EXTENDED
LOCAL s,t AS ASCIIZ*21
n=12345678912.3456789
'n=0
'n=1
'n=.1
'n=.001
EncodeExt n,s
DecodeExt m,s
EncodeExt m,t
v=m-n
MSGBOX s+$CR+t+$CR+"Value Difference check: "+STR$(v,18)
END FUNCTION
THAT is very cool Charles!
Considering that a DOUBLE is probably more than enough range for this project, I could use the EXTENDED data type and just round off all displayed numbers by using STR$ for the first 16 significant digits to avoid displaying rounding errors.
I included some addition and subtraction in the following test even tho division and multiplication are repeated addition and subtraction as I suspect the DECIMAL datatype may have some additional overhead for the mult and div operations.
This test assumes that I am required to pull a couple of values out of the database, perform a calculation and store the result. The DOUBLE can be stored and calculated directly. The DECIMAL would need some sort of TEXT/QUAD encoding method (TEXT encoding Functions used to simulate overhead). The EXTENDED type uses the TEXT encoding.
The results are interesting. The DECIMAL calculation speed varies significantly
For example,
Using 523.34 for Dec1 and 3 for Dec2
RetVal = VarDecMul(Dec1, Dec2, Dec3) '
RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
takes 520 clks yet
RetVal = VarDecMul(Dec1, Dec2, Dec3) '
RetVal = VarDecDiv(Dec3, Dec2, Dec1) '
takes only 120 Clks!
In this test I used your large EXTENDED number and got:
Clks= 166, DOUBLE VAL = 12345678912.3457
Clks= 197, EXTENDED VAL = 12345678912.3457
Clks= 915, TEXT EXT VAL =-12345678912.3456789 ' <------ I think there is a sign problem?
Clks= 1032, DECIMAL Result =12345678912.3457
#COMPILE EXE
#DIM ALL
#INCLUDE "Decimal.inc"
GLOBAL hDbg AS LONG
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! ADD esi,9 ' offset to end of number
'----------------'
! XOR BYTE PTR [esi],&h80 ' invert sign bit
! mov ecx,5 ' 5 loops to process 2 bytes and produce 4 bytes
' in each cycle
'----------------'
nex4: '
! mov dl,[esi] ' load digit (we go from msb to lsb)
! mov dh,dl ' prepare to split the nybbles
! shr dl,4 ' most significant nybble in dl
! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
! mov eax,edx ' move the result to eax
! dec esi ' decrement number digit pointer
'----------------'
! mov dl,[esi] ' get the next 2 nybbles
! mov dh,dl ' split them
! shr dl,4 ' dl to contain the highest
! AND edx,&h0f0f ' mask off unwanted bits
! shl edx,16 ' move along 2 bytes in the string
! ADD eax,edx ' combine with the previous 2 bytes
! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
! mov [edi],eax ' store the 4 bytes into the string
! ADD edi,4 ' advance the string pointer for next.
'----------------'
! dec esi ' decrement the number pointer
! dec ecx ' decrement the cycle counter
! jg nex4 ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
'----------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! ADD esi,9 ' offset to last byte of number
'----------------'
! mov ecx,5 '
nex4: '
! mov eax,[edi] '
! ADD edi,4 ' advance pointer for next 4 chars
! SUB eax,&h41414141 ' subtract ascii offsets
'----------------'
! mov dh,al ' get higher nybble
! shl dh,4 ' mov it up *16
! mov dl,ah ' hold it in dl
! AND dl,&hf ' mask off unwanded bits
! ADD dl,dh ' add higher to lower nybble
! mov [esi],dl ' store the recomposed byte
! dec esi ' work backwards towards lsb of number
! shr eax,16 ' shift right to get the next 2 chars
'----------------'
! mov dh,al ' store as higher nybble
! shl dh,4 ' shift into position
! mov dl,ah ' move to dl
! AND dl,&hf ' mask unwanted bits
! ADD dl,dh ' add higher to lower
! mov [esi],dl ' store the recomposed byte to the number
! dec esi ' move left for the next lower byte
! shr eax,16 ' shift for the next 2 chars
'----------------'
! dec ecx '
! jg nex4 '
! XOR BYTE PTR [esi+10], &h80 ' invert sign bit
'----------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count Charles e1 V Pegge
'---------------------------'
' ' approx because it is not a serialised instruction
' ' it may execute before or after other instructions
' ' in the pipeline.
! mov ebx,tick ' var address where count is to be stored.
! db &h0f,&h31 ' RDTSC read time-stamp counter into edx:eax hi lo.
! mov [ebx],eax ' save low order 4 bytes.
! mov [ebx+4],edx ' save high order 4 bytes.
'---------------------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
FUNCTION PBMAIN
LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG
LOCAL cBeg, cEnd AS QUAD ' for time stamp, measuring cpu clock cycles
LOCAL d1, d2 AS DOUBLE
LOCAL e1, e2 AS EXTENDED
LOCAL s, sTemp AS STRING
LOCAL Dec1, Dec2, Dec3 AS Decimal
LOCAL z1, z2 AS ASCIIZ*21
hDbg = FREEFILE '
OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)
nLoops = 1000000
e1 = 12345678912.3456789##
e2 = 523.34##
d1 = e1
d2 = e2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
d1 = d1 + d2
d1 = d1 * d2
d1 = d1 / d2
d1 = d1 - d2
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DOUBLE VAL =" + STR$(d1) + $CRLF + $CRLF
'=======================
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
e1 = e1 + e2
e1 = e1 * e2
e1 = e1 / e2
e1 = e1 - e2
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", EXTENDED VAL =" + STR$(e1) + $CRLF + $CRLF
'=======================
EncodeExt e1, z1
EncodeExt e2, z2
' s = s + "TEXT z1 Encoded="+z1 + ", TEXT z2 Encoded="+z2 + $CRLF + $CRLF
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
DecodeExt e1, z1
DecodeExt e2, z2
e1 = e1 + e2
e1 = e1 * e2
e1 = e1 / e2
e1 = e1 - e2
EncodeExt e1, z1
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", TEXT EXT VAL =" + STR$(e1,18) + $CRLF + $CRLF
'=======================
RetVal = VarDecFromR8(d1, Dec1) ' prepare Dec2
RetVal = VarDecFromR8(d2, Dec2) ' prepare Dec2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
DecodeExt e1, z1
DecodeExt e2, z2
RetVal = VarDecAdd(Dec1, Dec2, Dec3) '
RetVal = VarDecMul(Dec3, Dec2, Dec1) '
RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
RetVal = VarDecSub(Dec3, Dec2, Dec1) '
EncodeExt e1, z1
NEXT '
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
CALL VarBstrFromDec(Dec1, 0, 0, sTemp )
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DECIMAL Result =" + ACODE$(sTemp) + $CRLF + $CRLF
'=======================
PRINT #hDbg, s
MSGBOX s,64,"All Done" : EXIT FUNCTION
CLOSE hDbg
END FUNCTION
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Thanks Mike,
The problem was in the Encoder which reversed the sign of the original number. - Slipped through my tests!
I have also tweaked the test to use one encode and one decode only.
My results with an AMD64dual CPU are:
DOUBLE 71 clocks
EXTENDED 163 clocks
EXTENDED+TEXT ENCODING/DECODING 307 clocks
DECIMAL 444 clocks
(with the extra bytes this will expand to about 480 clocks)
#COMPILE EXE
#DIM ALL
#INCLUDE "Decimal.inc"
GLOBAL hDbg AS LONG
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB EncodeExt( n AS EXTENDED, s AS ASCIIZ )
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! ADD esi,9 ' offset to end of number
'----------------'
! mov ecx,5 ' 5 loops to process 2 bytes and produce 4 bytes
' in each cycle
'----------------'
nex4: '
! mov dl,[esi] ' load digit (we go from msb to lsb)
! mov dh,dl ' prepare to split the nybbles
! shr dl,4 ' most significant nybble in dl
! AND edx,&h0f0f ' mask unwanted bits to leave the nybbles
! mov eax,edx ' move the result to eax
! dec esi ' decrement number digit pointer
'----------------'
! mov dl,[esi] ' get the next 2 nybbles
! mov dh,dl ' split them
! shr dl,4 ' dl to contain the highest
! AND edx,&h0f0f ' mask off unwanted bits
! shl edx,16 ' move along 2 bytes in the string
! ADD eax,edx ' combine with the previous 2 bytes
! ADD eax,&h41414141 ' add 'A' to each nybble to encode 'A..P'
! mov [edi],eax ' store the 4 bytes into the string
! ADD edi,4 ' advance the string pointer for next.
'----------------'
! dec esi ' decrement the number pointer
! dec ecx ' decrement the cycle counter
! jg nex4 ' repeat cycle until 0 (10 bytes of number 20 bytes of code)
! xor byte ptr [edi-20],8 ' invert sign bit
'----------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB DecodeExt(n AS EXTENDED, s AS ASCIIZ)
#REGISTER NONE
'----------------'
! mov esi,n ' number pointer
! mov edi,s ' encoded string pointer
! ADD esi,9 ' offset to last byte of number
'----------------'
! mov ecx,5 '
nex4: '
! mov eax,[edi] '
! ADD edi,4 ' advance pointer for next 4 chars
! SUB eax,&h41414141 ' subtract ascii offsets
'----------------'
! mov dh,al ' get higher nybble
! shl dh,4 ' mov it up *16
! mov dl,ah ' hold it in dl
! AND dl,&hf ' mask off unwanded bits
! ADD dl,dh ' add higher to lower nybble
! mov [esi],dl ' store the recomposed byte
! dec esi ' work backwards towards lsb of number
! shr eax,16 ' shift right to get the next 2 chars
'----------------'
! mov dh,al ' store as higher nybble
! shl dh,4 ' shift into position
! mov dl,ah ' move to dl
! AND dl,&hf ' mask unwanted bits
! ADD dl,dh ' add higher to lower
! mov [esi],dl ' store the recomposed byte to the number
! dec esi ' move left for the next lower byte
! shr eax,16 ' shift for the next 2 chars
'----------------'
! dec ecx '
! jg nex4 '
! XOR BYTE PTR [esi+10], &h80 ' invert sign bit
'----------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
SUB time_stamp_count(tick AS QUAD) ' CPU Clock count Charles e1 V Pegge
'---------------------------'
' ' approx because it is not a serialised instruction
' ' it may execute before or after other instructions
' ' in the pipeline.
! mov ebx,tick ' var address where count is to be stored.
! db &h0f,&h31 ' RDTSC read time-stamp counter into edx:eax hi lo.
! mov [ebx],eax ' save low order 4 bytes.
! mov [ebx+4],edx ' save high order 4 bytes.
'---------------------------'
END SUB
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤'
FUNCTION PBMAIN
LOCAL i, Rounding, nLoops, RetVal, LONGvar AS LONG
LOCAL cBeg, cEnd AS QUAD ' for time stamp, measuring cpu clock cycles
LOCAL d1, d2 AS DOUBLE
LOCAL e1, e2 AS EXTENDED
LOCAL s, sTemp AS STRING
LOCAL Dec1, Dec2, Dec3 AS Decimal
LOCAL z1, z2 AS ASCIIZ*21
hDbg = FREEFILE '
OPEN "DecimalDebug.txt" FOR OUTPUT LOCK WRITE AS hDbg ' PRINT #hDbg, "MetersToFt="+STR$(MetersToFt)
nLoops = 1000000
e1 = 12345678912.3456789##
e2 = 523.34##
d1 = e1
d2 = e2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
d1 = d1 + d2
d1 = d1 * d2
d1 = d1 / d2
d1 = d1 - d2
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DOUBLE VAL =" + STR$(d1) + $CRLF + $CRLF
'=======================
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
e1 = e1 + e2
e1 = e1 * e2
e1 = e1 / e2
e1 = e1 - e2
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", EXTENDED VAL =" + STR$(e1) + $CRLF + $CRLF
'=======================
EncodeExt e1, z1
EncodeExt e2, z2
' s = s + "TEXT z1 Encoded="+z1 + ", TEXT z2 Encoded="+z2 + $CRLF + $CRLF
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
'DecodeExt e1, z1
'DecodeExt e2, z2
e1 = e1 + e2
e1 = e1 * e2
e1 = e1 / e2
e1 = e1 - e2
EncodeExt e1, z1
DecodeExt e1, z1
NEXT
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", TEXT EXT VAL =" + STR$(e1,18)+$CRLF +z1+ $CRLF+$CRLF
'=======================
RetVal = VarDecFromR8(d1, Dec1) ' prepare Dec2
RetVal = VarDecFromR8(d2, Dec2) ' prepare Dec2
time_stamp_count(cBeg) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
FOR i = 1 TO nLoops
EncodeExt e1, z1
DecodeExt e1, z1
RetVal = VarDecAdd(Dec1, Dec2, Dec3) '
RetVal = VarDecMul(Dec3, Dec2, Dec1) '
RetVal = VarDecDiv(Dec1, Dec2, Dec3) '
RetVal = VarDecSub(Dec3, Dec2, Dec1) '
'EncodeExt e1, z1
NEXT '
time_stamp_count(cEnd) ' measuring cpu clock cycles. The overhead just for making this call is about 25 clocks
CALL VarBstrFromDec(Dec1, 0, 0, sTemp )
s = s + "Clks="+STR$( (cEnd-cBeg)\nLoops ) + ", DECIMAL Result =" + ACODE$(sTemp) + $CRLF + $CRLF
'=======================
PRINT #hDbg, s
MSGBOX s,64,"All Done" : EXIT FUNCTION
CLOSE hDbg
END FUNCTION
'¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤