Jaki jest cel boksowania NaN?

44

Czytając 21 wiek C Doszedłem do rozdziału 6 w sekcji „Oznaczanie wyjątkowych wartości liczbowych za pomocą NaNs” , gdzie wyjaśnia użycie bitów w mantysie do przechowywania niektórych dowolnych wzorów bitów, wykorzystywania ich jako znaczników lub wskaźników (książka wspomina że WebKit korzysta z tej techniki).

Nie jestem do końca pewien, czy zrozumiałem użyteczność tej techniki, którą widzę jako hack (polega ona na sprzęcie nie dbającym o wartość mantysy w NaN), ale pochodzącym ze środowiska Java, do którego nie jestem przyzwyczajony szorstkość C.

Oto fragment kodu, który ustawia i odczytuje znacznik w NaN

#include <stdio.h>
#include <math.h> //isnan

double ref;

double set_na(){
    if (!ref) {
        ref=0/0.;
        char *cr = (char *)(&ref);
        cr[2]='a';
    }
    return ref;
}

int is_na(double in){
    if (!ref) return 0;  //set_na was never called==>no NAs yet.

    char *cc = (char *)(&in);
    char *cr = (char *)(&ref);
    for (int i=0; i< sizeof(double); i++)
        if (cc[i] != cr[i]) return 0;
    return 1;
}

int main(){
    double x = set_na();
    double y = x;
    printf("Is x=set_na() NA? %i\n", is_na(x));
    printf("Is x=set_na() NAN? %i\n", isnan(x));
    printf("Is y=x NA? %i\n", is_na(y));
    printf("Is 0/0 NA? %i\n", is_na(0/0.));
    printf("Is 8 NA? %i\n", is_na(8));
}

drukuje:

Is x=set_na() NA? 1
Is x=set_na() NAN? 1
Is y=x NA? 1
Is 0/0 NA? 0
Is 8 NA? 0

a na stronie JSValue.h webkit wyjaśnia kodowanie, ale nie wyjaśnia, dlaczego jest używane.

Jaki jest cel tej techniki? Czy zalety przestrzeni / wydajności są wystarczająco wysokie, aby zrównoważyć jej hackerski charakter?

andijcr
źródło
czy możesz podać prosty przykład?
BЈовић
dla wyjaśnienia, OP pyta, gdzie można zastosować sygnalizację NaNs
maniak zapadkowy
1
@ratchetfreak, co sprawia, że ​​tak myślisz?
Winston Ewert
@ratchetfreak: pytanie nie dotyczy sygnalizacji NaN, jak wyjaśnia webkit JSValue.h, ale dziękuję za umożliwienie mi odkrycia czegoś nowego!
andijcr
1
@Hudson isnan () si użyty w drugim printf w main. Celem is_an () jest sprawdzenie, czy wzór bitowy podwójnego wejścia jest taki sam, jak zapisany w zmiennej globalnej ref.
andijcr

Odpowiedzi:

63

Kiedy wdrażasz dynamicznie pisany język, musisz mieć jeden typ, który może pomieścić dowolny z twoich obiektów. Są trzy różne podejścia, o których wiem w tym zakresie:

Po pierwsze, możesz omijać wskaźniki. Tak właśnie działa implementacja CPython. Każdy obiekt jest PyObjectwskaźnikiem. Wskaźniki te są omijane, a operacje są wykonywane przez sprawdzenie szczegółów w strukturze PyObject w celu ustalenia typu.

Wadą jest to, że małe wartości, takie jak liczby, są przechowywane jako wartości pudełkowe, więc twoja mała 5 jest przechowywana gdzieś jako blok pamięci. To prowadzi nas do podejścia związkowego, z którego korzysta Lua. Zamiast a PyObject*każda wartość jest strukturą, której jedno pole określa typ, a następnie połączenie wszystkich obsługiwanych typów. W ten sposób unikamy przydzielania pamięci na małe wartości, zamiast przechowywania ich bezpośrednio w unii.

NaNWszystko przechowuje podejście jak deblu i ponownie wykorzystuje niewykorzystaną część NaNdla dodatkowej pamięci masowej. Zaletą metody łączenia jest to, że zapisujemy pole typu. Jeśli jest to poprawna podwójna, to podwójna, w przeciwnym razie mantysa jest wskaźnikiem do rzeczywistego obiektu.

Pamiętaj, to jest każdy obiekt javascript. Każda zmienna, każda wartość w obiekcie, każde wyrażenie. Jeśli uda nam się zredukować wszystkie z 96 do 64 bitów, robi to wrażenie.

Czy warto hack? Przypomnij sobie, że istnieje duże zapotrzebowanie na wydajny Javascript. JavaScript jest wąskim gardłem w wielu aplikacjach internetowych, dlatego przyspieszenie jest priorytetem. Rozsądne jest wprowadzenie pewnego stopnia włamań ze względu na wydajność. W większości przypadków byłby to zły pomysł, ponieważ wprowadza pewien stopień złożoności przy niewielkim zysku. Ale w tym konkretnym przypadku warto poprawić pamięć i prędkość.

Winston Ewert
źródło
2
W rzeczywistości CPython buforuje małe liczby. Zobacz hg.python.org/cpython/file/e6cc582cafce/Objects/longobject.c
Phillip Cloud
1
@ clcloud, prawda, ale ten szczegół nie wydawał się trafny.
Winston Ewert
1
@WinstonEwert Masz rację. Myślałem o tym samym po przeczytaniu tego, co napisałem.
Phillip Cloud,
2
Używanie bitów typu pierwotnego w celu uniknięcia „boksowania” wszystkich wartości jest uświęconą techniką. Smalltalk używał go w latach 70. XX wieku, kradnąc jeden bit z 16-bitowych liczb całkowitych, aby zasygnalizować wskaźnik obiektu lub 15-bit SmallInteger.
Jonathan Eunice,
2
@JonathanEunice, naprawdę? To mnie po prostu zaskakuje, ponieważ tak naprawdę nie ma dużego zasięgu w 16 bitach, z których byłbym skłonny trochę zrezygnować.
Winston Ewert
7

Używanie NaN do „wyjątkowych wartości” jest dobrze znaną i czasem pomocną techniką pozwalającą uniknąć potrzeby dodatkowej zmiennej boolowskiej this_value_is_invalid. Używana mądrze, może pomóc uczynić jego kod bardziej zwięzłym, czystszym, prostszym, lepiej czytelnym bez żadnych kompromisów wydajnościowych.

Ta technika ma oczywiście pewne pułapki (patrz tutaj http://ppkwok.blogspot.co.uk/2012/11/java-cafe-1-never-write-nan-nan_24.html ), ale w językach takich jak Java ( lub bardzo podobny C #) istnieją standardowe funkcje biblioteczne, takie jak Float.isNaNuproszczenie obsługi NaN. Oczywiście w Javie można użyć alternatywnie Floati Doubleklasy i C # pustych typy wartości float?i double?, co daje możliwość zastosowania nullzamiast NaN dla niepoprawnych liczb zmiennoprzecinkowych, ale te techniki mogą mieć znaczący wpływ negatywny na wydajność i pamięć korzystanie z twojego programu.

W C użycie NaN nie jest w 100% przenośne, to prawda, ale można go używać wszędzie tam, gdzie dostępny jest standard zmiennoprzecinkowy IEEE 754. AFAIK jest to prawie każdy obecnie główny sprzęt (lub przynajmniej środowisko uruchomieniowe większości kompilatorów go obsługuje). Na przykład ten post SO zawiera pewne informacje, aby dowiedzieć się więcej szczegółów na temat używania NaN w C.

Doktor Brown
źródło
auto-boxing w java jest niechlujny i należy go unikać, samo użycie go w celu zapewnienia wartości zerowej jest śmieszne i podatne na błędy
maniak ratchet
zredagowałem pytanie, aby utworzyć link do miejsca, w którym webkit używa boksowania NaN. Wygląda na to, że webkit ma szersze zastosowanie NaN, poza sygnalizowaniem „NaN”
andijcr
2
@ratchetfreak: to oczywiście popiera mój punkt
Doc Brown