Конвертировать Delphi Real48 в C # double

6

Мне нужно иметь возможность конвертировать из Delphi Real48 в С# double.

У меня есть байты, которые мне нужно преобразовать, но я ищу элегантное решение. к проблеме.

Кто-нибудь должен был сделать это раньше?

Мне нужно сделать преобразование в С#

Заранее спасибо

  • 2
    Люди все еще используют Real48? ПОЧЕМУ?!
  • 0
    Куда вам нужно их конвертировать? В программе Delphi?
Показать ещё 3 комментария
Теги:
double

5 ответов

8
Лучший ответ

Я сделал некоторую охоту вокруг, и я нашел код на С++, чтобы выполнить задание, преобразовал его и, кажется, дал правильный ответ... проклятый, если я все понял: 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);
    }

Если кто-то может объяснить это, было бы здорово: D

  • 0
    Байты с 1 по 5 представляют дробную часть числа в научной нотации: 1.x * 2^e . Мантисса составляет 1.x Цикл for и две следующие строки генерируют x . Предположим, байт 1 равен 0xa5. В двоичном виде это 10100101. Добавьте это к mantissa чтобы получить mantissa == 0xa5 . Затем сдвиньте эти байты вниз в дробную часть, чтобы получить двоичное значение 0.10100101. Сдвиг 8 делит на 256. Повторите для байтов со 2 по 4. Байт 5 особенный, поскольку нам нужно только 7 битов - восьмой бит является знаковым битом, поэтому вместо этого разделите на 128. Наконец, добавьте 1, поскольку эта часть неявна (нигде не хранится).
  • 0
    Байт 0 является показателем степени. Это число без знака, но оно смещено высоко на 129, поэтому первое, что нужно сделать, - исправить это смещение. Как упоминалось в предыдущем комментарии, число имеет вид 1.x * 2^e , где 1.x хранится в mantissa а e - в exponent . Последняя строка кода просто вычисляет это значение как двойное число.
Показать ещё 6 комментариев
3
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);
}
  • 0
    Из Delphi Basics: «Real48: устарел - тип с плавающей запятой с максимальной емкостью и точностью». В современных версиях Delphi это Extended (10 байт)
  • 0
    @ Darin, боюсь, это не дает правильного ответа
Показать ещё 3 комментария
2

Оцените, что это старый пост, но также может быть полезно следующее для тех, кто хочет сделать это в T-SQL (каким я был).

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

Подтверждение

0,125 - это 0x0000 0000 007E (или 0x 7E00 0000 0000 в обратном порядке)

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

Ввод - char12, поскольку мне пришлось извлечь двоичный файл из середины двух других более крупных двоичных полей и шунтировать их вместе, если бы он уже был char12. Достаточно легко перейти на двоичный (6) вход, если не нужно делать никаких манипуляций заранее.

Как в стороне, в сценарии, который я реализую, вариант T-SQL превзошел код С# CLR, поэтому код С# выше может быть лучше. Хотя не везде допускается использование кода CLR в SQL Server, если вы тогда, возможно, должны. Для получения дополнительной информации статья http://www.simple-talk.com/sql/t-sql-programming/clr-performance-testing/ делает некоторые измерения глубины, которые показывают некоторые существенные различия между T-SQL и CLR.

1

Я тестировал это и обнаружил ошибку (как заметили другие) с отрицательными значениями. Вот моя проверенная версия кода. Я тестировал это с 120,530 различными случайными значениями от 11 400 000,00 до -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;
0

Я изменил код, который вы разместили, в более читаемый формат, чтобы вы могли увидеть, как он работает:

        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
    Этот код вводит как минимум одну ошибку. Вы забыли проверить это с отрицательным входом.
  • 0
    Он ждал, чтобы ты проверил это для него, Роб. Спасибо!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню