【问题标题】:Convert Delphi Real48 to C# double将 Delphi Real48 转换为 C# double
【发布时间】:2010-03-24 10:41:49
【问题描述】:

我需要能够从 Delphi Real48 转换为 C# double。

我有需要转换的字节,但我正在寻找一个优雅的解决方案。解决问题。

以前有人必须这样做吗?

我需要在 C# 中进行转换

提前致谢

【问题讨论】:

  • 人们还在使用Real48?为什么?!
  • 你需要在哪里转换它们?在 Delphi 程序中?
  • @Ignacio:我想到了向后兼容性。
  • 别问我,这周之前我什至不知道 Real48 是什么。我正在转换一个 Delphi blob:s
  • 不能用Delphi写个转换工具吗? (我想 C# 的 double 在 Delphi 中有一个完全等价的。)

标签: c# delphi double


【解决方案1】:

我已经四处寻找,我找到了一些 C++ 代码来完成这项工作,并对其进行了转换,它似乎给出了正确的答案……如果我完全理解的话,该死的: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 到 5 表示科学计数法中数字的小数部分:1.x * 2^e。尾数是1.xfor 循环和以下两行生成 x。假设字节 1 是 0xa5。在二进制中,即 10100101。将其添加到 mantissa 以得到 mantissa == 0xa5。然后将这些字节移位到小数部分以获得二进制值0.10100101。移位 8 除以 256。重复字节 2 到 4。字节 5 很特殊,因为我们只需要 7 位——第八位是符号位——所以除以 128。最后加 1,因为那部分是 implicit (不存储在任何地方)。
  • 字节 0 是指数。这是一个无符号数,但偏高了 129,所以首先要做的是纠正这种偏差。前面评论中提到过,编号的形式是1.x * 2^e,其中1.x存储在mantissa中,e存储在exponent中。最后一行代码只是将该值计算为双精度值。
  • 未来读者请注意:我很确定这段代码有错误。一方面,它忽略了 real48[4] 处的字节值。建议谨慎。
  • @KevinA.Naudé 我很确定通过在 real48[4] 中包含字节值来修复错误。 (我将其添加到答案中)
  • @heinrich5991 您是否测试了更改?
【解决方案2】:
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);
}

【讨论】:

  • 来自 Delphi Basics:“Real48:Obsolete - 具有最高容量和精度的浮点类型。”在现代版本的 Delphi 中,这是一个扩展(10 字节)
  • @Darin,恐怕这似乎没有给出正确的答案
  • 我想检查 SizeOf(Real48) 确实产生 6。
  • 分配bits 时,您重新创建了双精度的位模式,但是当您将其传递给Convert.ToDouble 时,它会将其视为普通整数并将该 整数值 转换为双精度数,这与您从普通的 long-to-double 赋值语句中得到的相同。你可能想要的是BitConverter.ToDouble
【解决方案3】:

感谢这是一篇旧文章,但对于那些希望在 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 是 0x 0000 0000 007E(或 0x 7E00 0000 0000 反转)

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

输入是 char12,因为我必须从其他 2 个较大的二进制字段的中间提取二进制文件并将它们分流在一起,因此已经将其作为 char12。如果不需要事先进行任何操作,很容易更改为二进制(6)输入。

顺便说一句,在我正在实施的场景中,T-SQL 变体的性能优于 C# CLR 代码,因此上面的 C# 代码可能更好。虽然不是所有地方都允许 CLR 代码进入 SQL Server,但如果可以的话,也许你应该这样做。有关更多背景知识,http://www.simple-talk.com/sql/t-sql-programming/clr-performance-testing/ 上的文章做了一些深度测量,显示了 T-SQL 和 CLR 之间的一些显着差异。

【讨论】:

    【解决方案4】:

    我一直在对此进行测试,并发现了一个带有负值的错误(正如其他人所注意到的)。这是我测试过的代码版本。我用从 11,400,000.00 到 -2,000,000.00 的 120,530 个不同的随机值对此进行了测试

     //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;
    

    【讨论】:

      【解决方案5】:

      我已将您发布的代码更改为更易读的格式,以便您了解其工作原理:

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

      【讨论】:

      • 这段代码至少引入了一个错误。你忘了用负输入来测试这个。
      • 他在等你为他测试它 Rob。谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 2015-02-07
      • 2015-06-24
      • 1970-01-01
      • 1970-01-01
      • 2018-09-23
      • 1970-01-01
      相关资源
      最近更新 更多