2010-12-17 12 views
8

Chciałbym manipulować bitowym reprezentacji liczb zmiennoprzecinkowych w C#. BinaryWriter i BinaryReader zrobić to w ten sposób:Jak zdobyć bity "podwójnego" jako "długiego"

public virtual unsafe void Write(double value) 
{ 
    ulong num = *((ulong*) &value); 
    ... 
} 
public virtual unsafe double ReadDouble() 
{ 
    ... 
    ulong num3 = ...; 
    return *((double*) &num3); 
} 

Czy istnieje sposób to zrobić bez kodu niebezpieczny i bez obciążania rzeczywiście przy BinaryWriter i BinaryReader?

+0

Jeśli obawiasz się o szybkość, używanie unii jest około trzy razy szybsze niż wywoływanie interfejsów API 'BitConverter' w moich szybkich testach. –

Odpowiedz

3

Starasz się unikać niebezpieczny kod w ogóle, czy chcesz tylko alternatywę dla tych konkretnych metod na BinaryReader i BinaryWriter?

Można użyć BitConverter.DoubleToInt64Bits i BitConverter.Int64BitsToDouble, które są zaprojektowane, aby zrobić dokładnie to, co trzeba, chociaż myślę, że korzystają z tego samego niebezpieczny konwersję za kulisami jak BinaryReader/BinaryWriter metod.

+0

Dobra odpowiedź! Zastanawiam się, jak tęskniłem za tym, gdy spojrzałem na BitConvertera. W miarę możliwości unikam niebezpiecznego kodu, więc mogę ponownie użyć kodu w np. Silverlight, ale lubię używać najszybszego możliwego do zweryfikowania kodu. – Qwertie

6

można wykorzystywać byte[] BitConverter.GetBytes(double) i long BitConverter.ToInt64(byte[],int) (przejście 0 jako uruchomienia wskaźnika), lecz wewnętrznie IIRC te wykorzystują kodu niebezpieczny, oraz mają narzut tablicy. Wybierz swoją truciznę ...

+0

Myślę, że wystarczająco dobrze dla mnie. Widzę, że BitConverter ma również bajtowy [] -> długi konwerter, który jest prawdopodobnie szybszy niż ręczne łączenie bajtów. – Qwertie

+0

Sądzę, że możesz to zrobić bez wywołań API lub niebezpiecznego kodu. –

7

Innym sposobem jest użycie niestandardowego struct z wyraźną układ, który określa zarówno long i double pod offsetem 0. Jest to odpowiednik union w C.

coś takiego:

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Explicit)] 
struct DoubleLongUnion 
{ 
    [FieldOffset(0)] 
    public long Long; 
    [FieldOffset(0)] 
    public double Double; 
} 

Następnie użyj tego:

var union = new DoubleLongUnion(); 
union.Double = 1.234d; 
var longBytes = union.Long; 

to pozwoli uniknąć jakiegokolwiek kodu niebezpieczny i powinien wykonywać dość szybko także podczas wykonywania konwersji na stosie.

nie próbowałem/skompilowany to, ale liczyć to powinno działać :)

EDIT

Właśnie próbowałem i to działa. Wartość longBytes powyżej 4608236261112822104.

Niektóre inne wartości:

0d    -> 0L 
double.NaN  -> -2251799813685248L 
double.MinValue -> -4503599627370497L 
double.MaxValue -> 9218868437227405311L 

Oto metoda, która robi to, co chcesz:

public static long DoubleToLong(double d) 
{ 
    return new DoubleLongUnion { Double = d }.Long; 
} 
+0

Pod wieloma względami związki są formą niebezpiecznego kodu, ale to dobre rozwiązanie :) –

+0

Nie sądzę, że jest tutaj coś szczególnie niebezpiecznego. Przynajmniej nie zgodnie z niemożliwym do zweryfikowania znaczeniem "unsafe" w języku C#. Myślę, że niebezpieczne jest błędne określenie w .NET. –

+0

@Drew - IIRC co najmniej jedna struktura (może to być CF, SL lub MF) odmawia dotknięcia związków ze względu na ich charakter. –