2010-01-06 14 views
5

Czy ktoś ma pod ręką te fragmenty kodu, aby przekonwertować IEEE 754 double do bezpośrednio niższego (odp. Superior) float, bez zmiany lub zakładając nic na temat aktualnej zaokrąglania FPU za tryb?Konwersja double float bez opierania się na tryb FPU zaokrąglania

Uwaga: to ograniczenie prawdopodobnie oznacza, że ​​w ogóle nie należy używać FPU. Spodziewam się, że najprostszym sposobem zrobienia tego w tych warunkach jest odczytanie bitów podwójnego w 64-bitowej długości i praca z tym.

Można przyjąć endianness swojego wyboru dla prostoty i że dwukrotnie w pytaniu jest dostępna przez pole poniżej jedności d:

union double_bits 
{ 
    long i; 
    double d; 
}; 

chciałbym spróbować zrobić to sam, ale jestem pewien Wprowadziłbym trudne do zauważenia błędy dla liczb denormalizowanych lub ujemnych.

+0

na systemach glibc znaleźć ieee754.h pliku nagłówka, który definiuje związki dla pływających rodzajów punktowych i struktury bitfield, więc można pracować z mantysy i wykładnika łatwiejsze, przepraszam, ale nie mogę dać ci prawdziwy kod. – quinmars

Odpowiedz

3

myślę następujące prace, ale będę podać moje założenia pierwszy :

  • liczby zmiennoprzecinkowe są zapisywane w formacie IEEE-754 od implementacji,
  • Brak przepełnienia,
  • Masz dostępne nextafterf() (jest to określone w C99).

Ponadto, najprawdopodobniej ta metoda nie jest bardzo wydajna.

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

int main(int argc, char *argv[]) 
{ 
    /* Change to non-zero for superior, otherwise inferior */ 
    int superior = 0; 

    /* double value to convert */ 
    double d = 0.1; 

    float f; 
    double tmp = d; 

    if (argc > 1) 
     d = strtod(argv[1], NULL); 

    /* First, get an approximation of the double value */ 
    f = d; 

    /* Now, convert that back to double */ 
    tmp = f; 

    /* Print the numbers. %a is C99 */ 
    printf("Double: %.20f (%a)\n", d, d); 
    printf("Float: %.20f (%a)\n", f, f); 
    printf("tmp: %.20f (%a)\n", tmp, tmp); 

    if (superior) { 
     /* If we wanted superior, and got a smaller value, 
      get the next value */ 
     if (tmp < d) 
      f = nextafterf(f, INFINITY); 
    } else { 
     if (tmp > d) 
      f = nextafterf(f, -INFINITY); 
    } 
    printf("converted: %.20f (%a)\n", f, f); 

    return 0; 
} 

Na moim komputerze, drukuje:

Double: 0.10000000000000000555 (0x1.999999999999ap-4) 
Float: 0.10000000149011611938 (0x1.99999ap-4) 
tmp: 0.10000000149011611938 (0x1.99999ap-4) 
converted: 0.09999999403953552246 (0x1.999998p-4) 

Chodzi o to, że jestem przeliczania wartości double do wartości — float to może być mniejsza lub większa niż wartość podwójnej zależności od tryb zaokrąglania. Po przekonwertowaniu z powrotem na double możemy sprawdzić, czy jest on mniejszy lub większy od wartości początkowej. Następnie, jeśli wartość float nie jest we właściwym kierunku, patrzymy na następny numer float z przekonwertowanej liczby w kierunku oryginalnego numeru.

+0

Dziękuję bardzo za ten kod. Powoli nabrałem przekonania, że ​​jest to najmniej podatne na błędy rozwiązanie. Dzięki za wskazanie 'nextafterf' również, to jest znacznie lepsze niż w/dekrementowanie bitów' float', jak gdyby było 'int'. Aby zmniejszyć ryzyko, że 'f + 1' będzie równe' f', czy zamiast tego mogę napisać 'nextafterf (f, INFINITY)'? –

+0

Po prostu przeczytałem strony podręcznika, standardową wersję C i wypróbowałem, i wygląda na to, że powinien działać "INFINITY". –

+0

OK, zredagowałem mój post. Dziękuję za komentarz. –

3

do wykonania tej pracy dokładniej niż tylko ponownie połączyć mantysę i check wykładnik nieco za to uwagę:

http://www.mathworks.com/matlabcentral/fileexchange/23173

pozdrowienia

+0

Dzięki. Funkcja 'doubles2halfp' jest tak samo skomplikowana, jak się obawiałem, ale przynajmniej ma już połowę stałych, więc jest to dobry punkt wyjścia. –

+0

Użyłbym podanego kodu jako referencji i przepisałbym prostsze podejście, używając & >> follwed by lub, a następnie sprawdź bardzo małe i bardzo duże liczby. Przejrzyj liczbę zmian i pozycję bitową na pierwszy rzut oka z http://babbage.cs.qc.edu/IEEE-754/Decimal.html – stacker

2

Napisałem kod, aby zrobić to tutaj: https://stackoverflow.com/q/19644895/364818 i skopiowałem go poniżej dla Twojej wygody.

// d is IEEE double, but double is not natively supported. 
    static float ConvertDoubleToFloat(void* d) 
    { 
     unsigned long long x; 
     float f; // assumed to be IEEE float 
     unsigned long long sign ; 
     unsigned long long exponent; 
     unsigned long long mantissa; 

     memcpy(&x,d,8); 

     // IEEE binary64 format (unsupported) 
     sign  = (x >> 63) & 1; // 1 
     exponent = ((x >> 52) & 0x7FF); // 11 
     mantissa = (x >> 0) & 0x000FFFFFFFFFFFFFULL; // 52 
     exponent -= 1023; 

     // IEEE binary32 format (supported) 
     exponent += 127; // rebase 
     exponent &= 0xFF; 
     mantissa >>= (52-23); // left justify 

     x = mantissa | (exponent << 23) | (sign << 31); 
     memcpy(&f,&x,4); 

     return f; 
    } 
+0

Dzięki. Linia 'exponent & = 0xFF;' oznacza, że ​​gdy byłoby właściwe, aby zwrócić '± FLT_MAX' lub' ± inf', zamiast tego zwracany jest 'float' z dziwnym wykładnikiem (i denormalne wyniki również są wyłączone). –

Powiązane problemy