开发者

Convert Delphi Real48 to C# double

I need to be able to开发者_StackOverflow社区 convert from a Delphi Real48 to C# double.

I've got the bytes I need to convert but am looking for an elegant solution. to the problem.

Anybody out there had to do this before?

I'm needing to do the conversion in C#

Thanks in advance


I've done some hunting around and I found some C++ code to do the job, converted it and it seems to be giving the right answer... damned if I understand it all though :S

    private static double Real48ToDouble(byte[] real48)
    {

        if (real48[0] == 0)
            return 0.0; // Null exponent = 0

        double exponent = real48[0] - 129.0;
        double mantissa = 0.0;

        for (int i = 1; i < 5; i++) // loop through bytes 1-4
        {
            mantissa += real48[i];
            mantissa *= 0.00390625; // mantissa /= 256
        }


        mantissa += (real48[5] & 0x7F);
        mantissa *= 0.0078125; // mantissa /= 128
        mantissa += 1.0;

        if ((real48[5] & 0x80) == 0x80) // Sign bit check
            mantissa = -mantissa;

        return mantissa * Math.Pow(2.0, exponent);
    }

If somebody can explain it that would be great :D


static double GetDoubleFromBytes(byte[] bytes)
{
    var real48 = new long[6];
    real48[0] = bytes[0];
    real48[1] = bytes[1];
    real48[2] = bytes[2];
    real48[3] = bytes[3];
    real48[4] = bytes[4];
    real48[5] = bytes[5];

    long sign = (real48[0] & 0x80) >> 7;

    long significand = 
        ((real48[0] % 0x80) << 32) + 
         (real48[1] << 24) + 
         (real48[2] << 16) + 
         (real48[3] << 8) + 
         (real48[4]);

    long exponent = bytes[5];

    if (exponent == 0)
    {
        return 0.0;
    }

    exponent += 894;
    long bits = (sign << 63) + (exponent << 52) + (significand << 13);
    return BitConverter.Int64BitsToDouble(bits);
}


Appreciate this is an old post, but also the following may be useful for those looking to do this in T-SQL (which I was).

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ifn_HexReal48ToFloat]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
    drop function [dbo].[ifn_HexReal48ToFloat]
go

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

create function [dbo].[ifn_HexReal48ToFloat]
(
    @strRawHexBinary    char(12),       -- NOTE. Do not include the leading 0x
@bitReverseBytes    bit 
)
RETURNS FLOAT
AS
BEGIN

-- Reverse bytes if required
-- e.g. 3FF4 0000 0000 is stored as
--      0000 0000 F43F
declare @strNewValue    varchar(12)
if @bitReverseBytes = 1
begin   
    set @strNewValue='' 
    declare @intCounter int
    set @intCounter = 6

    while @intCounter>=0
    begin
        set @strNewValue = @strNewValue + substring(@strRawHexBinary, (@intCounter * 2) + 1,2) 
        set @intCounter = @intCounter - 1
    end 
end

-- Convert the raw string into a binary
declare @binBinaryFloat binary(6)
set @binBinaryFloat = convert(binary(6),'0x' + isnull(@strNewValue, @strRawHexBinary),1)

-- Based on original hex to float conversion at http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=81849
-- and storage format documented at 
-- http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/internaldataformats_xml.html
-- Where, counting from the left
-- Sign         = bit 1
-- Exponent     = bits 41 - 48      with a bias of 129
-- Fraction     = bits 2 - 40


return

    SIGN
    (
        CAST(@binBinaryFloat AS BIGINT)
    )
    * 
    -- Fraction part. 39 bits. From left 2 - 40. 
    (
        1.0 + 
        (CAST(@binBinaryFloat AS BIGINT) & 0x7FFFFFFFFF00) * POWER(CAST(2 AS FLOAT), -47)
)
* 
    -- Exponent part. 8 bits. From left bits 41 -48
    POWER
    (
        CAST(2 AS FLOAT), 
        (
            CAST(@binBinaryFloat AS BIGINT) & 0xff
            - 129 
        ) 
    )

end

Confirmation

0.125 is 0x 0000 0000 007E (or 0x 7E00 0000 0000 reversed)

select dbo.ifn_HexReal48ToFloat('00000000007E', 0)
select dbo.ifn_HexReal48ToFloat('7E0000000000', 1) 

The input is a char12 as I had to extract the binary from the middle of 2 other larger binary fields and shunt them together so had it already as char12. Easy enough to change to be binary(6) input if don't need to do any manipulation beforehand.

As an aside, in the scenario I'm implementing into, the T-SQL variant is outperformed by C# CLR code so the C# code above may be better. Whilst not everywhere allows CLR code into SQL Server if you can then maybe you should. For more background an article at http://www.simple-talk.com/sql/t-sql-programming/clr-performance-testing/ does some in depth measurement which shows some dramatic differences between T-SQL and CLR.


I have been testing this and have found an error (as others have noticed) with negative values. Here is my tested version of the code. I tested this with 120,530 different random values ranging from 11,400,000.00 to -2,000,000.00

 //This seems to be the layout of the Real48 bits where
        //E = Exponent
        //S = Sign bit
        //F = Fraction

        //EEEEEEEE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF SFFFFFFF
        //12345678 12345678 12345678 12345678 12345678 12345678


        Double exponentbase = 129d;  // The exponent is offest by 129
        Double exponent = real48[0] - exponentbase; // deduct the offest. 

        // Calculate the mantissa 
        Double mantissa = 0.0;
        Double value = 1.0;

        // For Each Byte. 
        for (int iByte = 5; iByte >= 1; iByte--)
        {
            int startbit = 7;
            if (iByte == 5)
            { startbit = 6; } //skip the sign bit. 

            //For Each Bit 
            for (int iBit = startbit; iBit >= 0; iBit--)
            {
                value = value / 2;// Each bit is worth half the next bit but we're going backwards. 
                if (((real48[iByte] >> iBit) & 1) == 1) //if this bit is set. 
                {
                    mantissa += value; // add the value. 
                }

            }
        }

        if (mantissa == 1.0 && real48[0] == 0) // Test for null value 
            return 0.0;

        double result;

        result = (1 + mantissa) * Math.Pow(2.0, exponent);

        if ((real48[5] & 0x80) == 0x80) // Sign bit check 
            result = -result;

        return result;


I've changed the code you've posted into a more readable format so you can see how it works:

        Double exponentbase = 129d;
        Double exponent = real48[0] - exponentbase; // The exponent is offest so deduct the base.

        // Now Calculate the mantissa
        Double mantissa = 0.0;
        Double value = 1.0;
        // For Each Byte.
        for (int i = 5; i >= 1; i--)
        {
            int startbit = 7;
            if (i == 5)
            { startbit = 6; } //skip the sign bit.

            //For Each Bit
            for (int j = startbit; j >= 0; j--)
            {
                value = value / 2;// Each bit is worth half the next bit but we're going backwards.
                if (((real48[i] >> j) & 1) == 1) //if this bit is set.
                {
                    mantissa += value; // add the value.
                }

            }
        }

        if (mantissa == 1.0 && real48[0] == 0) // Test for null value
            return 0.0;

        if ((real48[5] & 0x80) == 1) // Sign bit check
            mantissa = -mantissa;

        return (1 + mantissa) * Math.Pow(2.0, exponent);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜