Мне нужно иметь возможность конвертировать из Delphi Real48 в С# double.
У меня есть байты, которые мне нужно преобразовать, но я ищу элегантное решение. к проблеме.
Кто-нибудь должен был сделать это раньше?
Мне нужно сделать преобразование в С#
Заранее спасибо
Я сделал некоторую охоту вокруг, и я нашел код на С++, чтобы выполнить задание, преобразовал его и, кажется, дал правильный ответ... проклятый, если я все понял: 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
1.x * 2^e
. Мантисса составляет 1.x
Цикл for и две следующие строки генерируют x . Предположим, байт 1 равен 0xa5. В двоичном виде это 10100101. Добавьте это к mantissa
чтобы получить mantissa == 0xa5
. Затем сдвиньте эти байты вниз в дробную часть, чтобы получить двоичное значение 0.10100101. Сдвиг 8 делит на 256. Повторите для байтов со 2 по 4. Байт 5 особенный, поскольку нам нужно только 7 битов - восьмой бит является знаковым битом, поэтому вместо этого разделите на 128. Наконец, добавьте 1, поскольку эта часть неявна (нигде не хранится).
1.x * 2^e
, где 1.x
хранится в mantissa
а e
- в exponent
. Последняя строка кода просто вычисляет это значение как двойное число.
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);
}
Оцените, что это старый пост, но также может быть полезно следующее для тех, кто хочет сделать это в 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.
Я тестировал это и обнаружил ошибку (как заметили другие) с отрицательными значениями. Вот моя проверенная версия кода. Я тестировал это с 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;
Я изменил код, который вы разместили, в более читаемый формат, чтобы вы могли увидеть, как он работает:
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);