A while ago I mentioned that System.BitConverter.Int64BitsToDouble would be useful for converting Pascal 6-byte real’s to IEEE 8-byte double’s. Here is my C# .Net code to do just that.
My original code was based on Richard Biffl’s BPREAL.C. My original code used System.Runtime.InteropServices.StructLayout( LayoutKind.Explicit ) attribute to make a C++ union data type. The new code now uses a long (int64) and System.BitConverter.Int64BitsToDouble to do the dirty work.
I originally had my real’s loaded into registers CX, SI, and DI, so that is why the name/order of my functions parameters also. I knew I had it correct when two of the original values turned out to be 2*PI and 100.0
Here’s the code, the old code is in class PascalReal and the newer version in class PascalRealNicer:
using System;
using System.Runtime.InteropServices;
namespace RealToDouble
{
class MainProgram
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0xdc83, /*si*/0xcf80, /*di*/0x490F })); // 2 * PI
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0x0087, /*si*/0x0000, /*di*/0x4800 })); // 100.0
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0x0084, /*si*/0x0000, /*di*/0xa000 })); // -10.0
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0x0084, /*si*/0x0000, /*di*/0x0000 })); // 8.0
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0x0083, /*si*/0x0000, /*di*/0x0000 })); // 2.0
Console.WriteLine("{0}", PascalReal.ToDouble(new ushort[] { /*cx*/0x0000, /*si*/0x0000, /*di*/0x0000 })); // 0.0
Console.WriteLine();
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0xdc83, /*si*/0xcf80, /*di*/0x490F)); // 2 * PI
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0x0087, /*si*/0x0000, /*di*/0x4800)); // 100.0
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0x0084, /*si*/0x0000, /*di*/0xa000)); // -10.0
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0x0084, /*si*/0x0000, /*di*/0x0000)); // 8.0
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0x0083, /*si*/0x0000, /*di*/0x0000)); // 2.0
Console.WriteLine("{0}", PascalRealNicer.ToDouble( /*cx*/0x0000, /*si*/0x0000, /*di*/0x0000)); // 0.0
Console.ReadKey();
}
}
class PascalReal
{
[ StructLayout( LayoutKind.Explicit )]
public struct doublearray
{
[ FieldOffset( 0 )]
public ushort a0;
[ FieldOffset( 2 )]
public ushort a1;
[ FieldOffset( 4 )]
public ushort a2;
[ FieldOffset( 6 )]
public ushort a3;
[ FieldOffset( 0 )]
public double d;
}
public static double ToDouble( ushort[] r )
{
doublearray da;
da.d = 0.0;
da.a0 = 0;
da.a1 = 0;
da.a2 = 0;
da.a3 = 0;
ushort x = (ushort)(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.a3 = (ushort)(((x + 894) << 4) | /* adjust exponent bias */
(r[2] & 0x8000) | /* sign bit */
((r[2] & 0x7800) >> 11)); /* begin significand */
da.a2 |= (ushort)((r[2] << 5) | /* continue shifting significand */
(r[1] >> 11));
da.a1 |= (ushort)((r[1] << 5) |
(r[0] >> 11));
da.a0 |= (ushort)((r[0] & 0xFF00) << 5); /* mask real's exponent */
}
return da.d;
}
}
class PascalRealNicer
{
public static double ToDouble(ushort cx, ushort si, ushort di)
{
long double64 = 0;
int exp = cx & 0x00ff;
if (exp != 0)
{
// sign
double64 = (long)(di & 0x8000) << 48;
// biased exponent
double64 |= (long)(exp + 894) << 52;
// mantissa
double64 |= (long)(di & 0x7FFF) << 37;
double64 |= (long)si << 21;
double64 |= (long)(cx & 0xFF00) << 5;
}
return System.BitConverter.Int64BitsToDouble(double64);
}
}
}