I am connecting to a Pervasive SQL database which splits some data over two fields. DOUBLE fields are actually split into fieldName_1 and fieldName_2 where _1 is a 2 byte int and _2 is a 4 byte int.
I want to take these values and convert them using PHP into a usable value. I have some example code to do the conversion, but it is written in Delphi which I do not understand:
{ Reconstitutes a SmallInt and LongInt that form }
{ a Real into a double. }
Function EntConvertInts (Const Int2 : SmallInt;
Const Int4 : LongInt) : Double; StdCall;
Var
TheRealArray : Array [1..6] Of Char;
TheReal : Real;
Begin
Move (Int2, TheRealArray[1], 2);
Move (Int4, TheRealArray[3], 4);
Move (TheRealArray[1], TheReal, 6);
Result := TheReal;
End;
Some data [fieldName_1,fieldName_2]
[132, 805306368] -> this should be 11
[132, 1073741824] -> this should be 12
I don't understand the logic enough to be able to port this into PHP. Any help would be most appreciated. Thanks
EDIT. This is the C code that they provided, showing sign/exponent:
double real_to_double (real r)
/* takes Pascal real, return C double */
{
union doublearray da;
unsigned x;
x = r[0] & 0x00FF; /* Real biased exponent in x */
/* when exponent is 0, value is 0.0 */
if (x == 0)
da.d = 0.0;
else {
da.a[3] = ((x + 894) << 4) | /* adjust exponent bias */
(r[2] & 0x8000) | /* sign bit */
((r[2] & 0x7800) >> 11); /* begin significand */
da.a[2] = (r[2] << 5) | /* continue shifting significand */
(r[1] >> 11);
da.a[1] = (r[1] << 5) |
(r[0] >> 11);
da.a[0] = (r[0] & 0xFF00) << 5; /* mask real's exponent */
}
return da.d;
}
Adding this as another answer because I've finally figured this out. Here is PHP code which will convert the values. It has to be manually calculated because PHP does not know how to unpack a Real48 (non standard). Explanation in comments below.
That is nor answer in "PHP code" sense. I just wanted to warn any person who maybe would find this code by Delphi tag.
THAT WAS NOT DELPHI !!!
It is old Turbo Pascal code. Okay, maybe 16-bit Delphi 1, which really was TP on steroids.
Don't try this code on 32-bit Delphi, at least not before replacing Char and Real types that changed. Both those types are changed from Turbo Pascal times, especially 6-byte Real which never was hardware FPU-compatible!
Probably FreePascal can bear vanilla TurboPascal code if settled to proper mode, but better still use Delphi mode and updated code.
One should also ensure that SmallInt type is 16-bit integer (int16) and LongInt is 32-bit(int32). This seemes to hold for 16-bit, 32-bit and 64-bit Delphi compilers, yet probably may change in other Pascal implementations.
Below i try to modify code compatible with modern Delphi. I was not able to test it though.
Hopefully that might help someone someday covert some similat old type-casting TurboPascal code to newer flavours.
This code is directly following original one, yet more compatible, concise and fast.
This code is directly using native Turbo Pascal features tagless variant record
Just spinning on J...'s answer. Utilizing a variant record the code is somewhat simplified :
Delphi's
Move
command is used for moving blocks of memory from one place to another. This looks like old Delphi code - theReal
type is obsolete, replaced withDouble
(editReal48
replaces 6-byteReal
), and theByte
type is probably a better one to use thanChar
. Both are bytes, but Char is more meant for single byte characters (ascii). What this code is doing is:1) Declare an array of Char(could use
Byte
here) which is six bytes in length. Also declare aReal
(edit nowReal48
type) to store the converted value.2) Move the two-byte Int value TO TheRealArray - start at index1 and move 2 bytes of data (ie: all of Int2, a SmallInt (16-bits)). Do the same with Int4 and start it at index [3], 4 bytes long.
if you started with (picture, not code)
you would have:
The final move command copies this array to the memory location of
TheReal
, which is a real (6-byte float) type. It starts at index1 of the array, copies it toTheReal
, and copies a total of six bytes (ie:the whole thing).Assuming that the data stored in Int2 and Int4, when concatenated like this, produce a properly formatted Real48 then you end up with TheReal holding the data in the proper format.
in PHP strings are fundamentally byte arrays (like Array[1..6] of Char in Delphi) so you could do the something similar using unpack() to convert to float.
I've been working on this issue for about a week now trying to get it sorted out for our organisation.
Our Finance dept use IRIS Exchequer and we need to get costs out. Using the above PHP code, I managed to get it working in Excel VBA with the following code (includes dependent functions). If not properly attributed below, I got all the long dec to bin functions from www.sulprobil.com. If you copy and paste the following code block into a Module you can reference my ExchequerDouble function from a cell.
Before I continue, I have to point out one error in the C/PHP code above. If you look at the Significand loops:
I noticed during testing that the answers were very close but often incorrect. Drilling further down I narrowed it down to the Significand. It might be a problem with translating the code from one language/methodology to another, or may have simply been a typo, but adding that (1 - i) made all the difference.
This code works, but sometimes (around 1% of my test data) you end up a couple pennies out compared to Iris' EntDouble function from the Excel Addin. I'll attribute this to precision, unless someone can figure it out.
Ultimately getting this working in VBA was my proof of concept to check everything worked. The intended platform for this functionality was SQL Server. If you have your Exchequer DB linked to a SQL Server you should be able to run this function directly against the data from the Pervasive DB. In my case, we are going to dump out the last 2.5 years worth of transaction data into a static table on SQL Server, but we're only working with this data once a year so it's not an issue. The following two functions should sort you out. In terms of precision, they are equivalent to the VBA code above with some being out by a couple pennies sometimes, but it seems 99% of the time it's exactly the same. We use SQL Server 2000 so there are some things that can probably be optimised (Varchar(MAX) for one) for newer versions but ultimately this should work fine as far as I know.
Feel free to use either my VBA or SQL code. The truly hard work was done by whoever converted it to PHP above. If anyone finds any way of improving anything please do let me know so we can make this code as perfect as possible.
Thanks!