Pascal 6-byte real to IEEE 8-byte double

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);
        }
    }
}