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);
精彩评论