2010-03-24 11 views
6

Devo essere in grado di convertire da Delphi Real48 a C# double.Conversione da Delphi Real48 a C# double

Ho i byte che ho bisogno di convertire ma sto cercando una soluzione elegante. al problema

Qualcuno là fuori doveva farlo prima?

ho bisogno di fare la conversione in C#

Grazie in anticipo

+2

Le persone usano ancora Real48? PERCHÉ?! –

+0

Dove hai bisogno di convertirli? In un programma Delphi? –

+3

@Ignacio: mi viene in mente la compatibilità con le versioni precedenti. –

risposta

8

Ho fatto qualche ricerca in giro e ho trovato un po 'di codice C++ per fare il lavoro, convertito esso e sembra dare la risposta giusta ... dannato se ho capito tutto però: 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); 
    } 

Se qualcuno può spiegare che sarebbe grande: D

+0

I byte da 1 a 5 rappresentano la parte frazione di un numero in notazione scientifica: '1.x * 2^e'. La mantissa è '1.x'. Il ciclo * for * e le due righe seguenti generano * x *. Supponiamo che il byte 1 sia 0xa5. In binario, è 10100101. Aggiungilo a 'mantissa' per ottenere' mantissa == 0xa5'. Quindi * spostare * quei byte verso il basso nella parte frazionaria per ottenere il valore binario 0.10100101. Shifting 8 si divide per 256. Ripeti per byte da 2 a 4. Il byte 5 è speciale dato che vogliamo solo 7 bit - l'ottavo bit è il bit del segno - quindi dividi invece per 128. Infine aggiungi 1 poiché quella parte è * implicita * (non memorizzata da nessuna parte). –

+0

Il byte 0 è l'esponente. È un numero senza segno, ma è di qualità superiore di 129, quindi la prima cosa da fare è correggere questo pregiudizio. Come menzionato nel commento precedente, il numero è nella forma '1.x * 2^e', dove' 1.x' è memorizzato in 'mantissa' e' e' è memorizzato in 'esponente'. L'ultima riga di codice calcola semplicemente quel valore come un doppio. –

+2

Nota per i futuri lettori: sono abbastanza sicuro che questo codice abbia errori. Per prima cosa, ignora il valore del byte su real48 [4]. Attenzione consigliata. –

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

Da Delphi Nozioni di base: "Real48: obsoleto - Il tipo a virgola mobile con la massima capacità e precisione." Nelle versioni moderne di Delphi che è un esteso (10 byte) –

+0

@Darin, temo che questo non sembra dare la risposta corretta –

+0

In effetti sembra che ci sia qualcosa di sbagliato. Controllerò di nuovo. –

0

Ho cambiato il codice che hai postato in un formato più leggibile in modo da poter vedere come funziona:

 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

Questo codice introduce almeno un errore. Hai dimenticato di testare questo con input negativi. –

+0

Ti stava aspettando per testarlo per lui Rob. Grazie! – Pauk

2

apprezzare questo è un vecchio post, ma anche quanto segue può essere utile per coloro che cercano per farlo in T-SQL (che ero).

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 

Conferma

0,125 0x 0000 0000 007E (o 0x 7E00 0000 0000 invertito)

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

l'ingresso è un char12 come ho dovuto estrarre il binario da metà 2 altri campi binari più grandi e li shunt insieme così lo aveva già come char12. Abbastanza facile da cambiare per essere un input binario (6) se non è necessario eseguire alcuna manipolazione in anticipo.

Per inciso, nello scenario in cui sto implementando, la variante T-SQL è sovraperformata dal codice CLR C# in modo che il codice C# sopra possa essere migliore. Anche se non dappertutto consente il codice CLR in SQL Server se è quindi possibile che si dovrebbe. Per ulteriori informazioni, un articolo su http://www.simple-talk.com/sql/t-sql-programming/clr-performance-testing/ esegue una misurazione in profondità che mostra alcune differenze notevoli tra T-SQL e CLR.

1

L'ho provato e ho riscontrato un errore (come altri hanno notato) con valori negativi. Ecco la mia versione testata del codice. Ho provato questo con 120.530 valori casuali diversi che vanno da 11.400.000,00 a -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;