2010-03-24 4 views
6

Je dois pouvoir convertir d'un Delphi Real48 en C# double.Convertir Delphi Real48 en C# double

J'ai les octets dont j'ai besoin pour convertir mais je recherche une solution élégante. au problème.

Quelqu'un devait-il faire ça auparavant?

Je besoin de faire la conversion en C#

Merci à l'avance

+2

Les gens utilisent toujours Real48? POURQUOI?! –

+0

Où avez-vous besoin de les convertir? Dans un programme Delphi? –

+3

@Ignacio: La rétrocompatibilité vient à l'esprit. –

Répondre

8

Je l'ai fait un peu de chasse autour et j'ai trouvé un peu de code C++ pour faire le travail, l'a converti et semble donner la bonne réponse ... damné si je comprends bien tout: 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); 
    } 

Si quelqu'un peut expliquer ce serait génial: D

+0

Les octets 1 à 5 représentent la fraction d'un nombre en notation scientifique: '1.x * 2^e'. La mantisse est '1.x'. La boucle * for * et les deux lignes suivantes génèrent * x *. Supposons que l'octet 1 est 0xa5. En binaire, c'est 10100101. Ajoutez cela à 'mantissa' pour obtenir' mantissa == 0xa5'. Puis * décale * ces octets dans la partie fractionnaire pour obtenir la valeur binaire 0.10100101. Shifting 8 est divisé par 256. Répéter pour les octets 2 à 4. Octet 5 est spécial car nous voulons seulement 7 bits - le huitième bit est le bit de signe - donc diviser par 128 à la place. Enfin, ajoutez 1 puisque cette partie est * implicite * (non stockée nulle part). –

+0

L'octet 0 est l'exposant. C'est un nombre non signé, mais il est biaisé de 129, donc la première chose à faire est de corriger ce biais. Comme mentionné dans le commentaire précédent, le nombre est sous la forme '1.x * 2^e', où' 1.x' est stocké dans 'mantisse' et' e' est stocké dans 'exponent'. La dernière ligne de code calcule simplement cette valeur comme un double. –

+2

Note aux futurs lecteurs: Je suis presque certain que ce code contient des erreurs. D'une part, il ignore la valeur d'octet à real48 [4]. Attention conseillée. –

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

De Delphi Basics: "Real48: Obsolète - Le type à virgule flottante avec la plus grande capacité et précision." Dans les versions modernes de Delphi c'est un Extended (10 Bytes) –

+0

@Darin, je crains que cela ne semble pas donner la bonne réponse –

+0

En effet, il semble y avoir quelque chose de mal. Je vais vérifier à nouveau. –

0

J'ai changé le code que vous avez posté dans un format plus lisible afin que vous puissiez voir comment cela fonctionne:

 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

Ce code introduit au moins une erreur. Vous avez oublié de tester cela avec une entrée négative. –

+0

Il vous attendait pour le tester pour lui Rob. Merci! – Pauk

2

apprécier c'est un ancien poste, mais aussi les éléments suivants peuvent être utiles pour ceux qui cherchent pour faire cela en T-SQL (que j'étais).

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 est 0x 0000 0000 007E (ou 0X 7E00 0000 0000 inversés)

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

l'entrée est un char12 comme je l'ai eu pour extraire le fichier binaire à partir du milieu de 2 d'autres plus grands champs binaires et les shunt ensemble ainsi l'avait déjà comme char12. Assez facile à changer pour être binaire (6) d'entrée si vous n'avez pas besoin de faire une manipulation à l'avance. En outre, dans le scénario que j'implémente, la variante T-SQL est surclassée par le code C# CLR, donc le code C# ci-dessus peut être meilleur. Bien que pas partout permet le code CLR dans SQL Server si vous pouvez alors peut-être vous devriez. Pour plus d'informations, un article au http://www.simple-talk.com/sql/t-sql-programming/clr-performance-testing/ fait une mesure en profondeur qui montre certaines différences dramatiques entre T-SQL et CLR.

1

Je l'ai testé et j'ai trouvé une erreur (comme d'autres l'ont remarqué) avec des valeurs négatives. Voici ma version testée du code. J'ai testé cela avec 120 530 différentes valeurs aléatoires allant de 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;