Rejestry jednostki zmiennoprzecinkowej
Koprocesor dysponuje ośmioma 80-bitowymi rejestrami zmiennoprzecinkowymi i trzema 16-bitowymi rejestrami kontrolnymi (control word, status word i tag word). Rejestry zmiennoprzecinkowe połączone są w stos. Możemy wczytywać do nich wartości rozkazami fld
(Fpu LoaD) oraz pobierać z nich wartości – fstp
(FPU STore and Pop). Mamy do nich dostęp poprzez nazwę ST i numer rejestru – ST(n). Wartości zawsze wstawiane są do rejestru ST(0), a wszystkie pozostałe rejestry zmieniają wtedy swoją numerację – 0 przechodzi na 1, 1 na 2 itd. Analogicznie podczas pobierania wartości z tego “stosu”, pobierana jest wartość z rejestru ST(0), a pozostałe zmieniają numerację w przeciwnym kierunku.
Podstawowe rozkazy jednostki zmiennoprzecinkowej
Rozkaz | Opis |
---|---|
fld REJESTR_ST | Ładuje zawartość innego podanego rejestru ST do ST(0) i przesuwa numerację wszystkich rejestrów, włącznie z tym z którego pobierana jest wartość, w górę. |
fldl ZMIENNA/ADRES | Podobnie jak poprzednio jednak ładowany jest float lub double z pamięci określonej przez etykietę zmiennej lub adres w rejestrze (wtedy mnemonik rejestru należy umieścić w nawiasie). |
fld1 | Do ST(0) ładowana jest stała 1.0, a pozostałe rejestry są “przesuwane”. |
fldz | -||- 0.0 -||- |
fldpi | -||- liczba PI (3.1415926) -||- |
fst REJESTR_ST | Wartość z rejestru ST(0) jest umieszczana w innym rejestrze ST. Numeracja rejestrów nie ulega zmianie. |
fstp REJESTR_ST lub nic | Wartość z rejestru ST(0) jest kopiowana do innego podanego rejestru lub tracona, następnie wszystkie rejestry zmieniają swoją numerację (1->0, 2->1 itd.). |
fstpl ZMIENNA/ADRES | Wartość z rejestru ST(0) kopiowana jest do zmiennej lub do pamięci pod adres sprecyzowany w innym rejestrze ogólnego przeznaczenia podanym w nawiasie. Numeracja rejestrów zmienia się jak poprzednio. |
fxch REJESTR_ST | Zawartości rejestru ST(0) i innego podanego jako operand rozkazu są wymieniane. |
fabs | Oblicza wartość bezwzględną z liczby w ST(0) i zapisuje ją do ST(0). |
fadd CEL, ŹRÓDŁO | Dodaje zawartość ŹRÓDŁO do CEL i zapisuje wynik do CEL. ŹRÓDŁO i CEL to mnemoniki rejestrów ST. |
faddl ZMIENNA/ADRES | Dodaje do ST(0), liczbę z pamięci. |
fsub CEL, ŹRÓDŁO | Odejmuje zawartość ŹRÓDŁO od CEL i zapisuje wynik do CEL. ŹRÓDŁO i CEL to mnemoniki rejestrów ST. |
fsubl ZMIENNA/ADRES | Odejmuje liczbę z pamięci od ST(0). |
fmul CEL, ŹRÓDŁO | Mnoży zawartość CEL przez ŹRÓDŁO i zapisuje wynik do CEL. ŹRÓDŁO i CEL to mnemoniki rejestrów ST. |
fmull ZMIENNA/ADRES | Mnoży zawartość ST(0) przez liczbę z pamięci. |
fdiv CEL, ŹRÓDŁO | Dzieli zawartość CEL przez ŹRÓDŁO i zapisuje wynik do CEL. ŹRÓDŁO i CEL to mnemoniki rejestrów ST. |
fdivl ZMIENNA/ADRES | Dzieli zawartość ST(0) przez liczbę z pamięci. |
fsqrt | Oblicza pierwiastek z liczby zapisanej w ST(0) i zapisuje wynik do ST(0). |
Rejestr kontrolny
W rejestrze kontrolnym (control word) zapisane są ustawienia jednostki FPU. Przy pomocy rozkazu fstcw
można zapisać jego zawartość do pamięci, a następnie instrukcją mov
przenieść do rejestru ogólnego przeznaczenia. Poszczególne bity tego rejestru odpowiadają min. za sposób zaokrąglania wartości, czy dokładność obliczeń. Możemy je zmienić, a następnie zapisać do rejestru w analogiczny sposób, umieszczając jego zawartość w pamięci i rozkazem fldcw
ładując ustawienia. Zostało to dokładniej przedstawione w zadaniu pierwszym i drugim, gdzie odczytywane i zmieniane są parametry pracy jednostki zmiennoprzecinkowej.
Zadanie pierwsze – Wyświetlanie i zmiana precyzji obliczeń
Kod C:
#include <stdio.h>
// Deklaracja funkcji które zostaną dołączone
// dopiero na etapie linkowania kodu
extern int sprawdz();
extern int ustaw(int precyzja);
int main(void)
{
// Wyświetlenie menu, pobranie od użytkownika numeru opcji
// i wyświetlenie lub ustawienie precyzji obliczeń korzystając
// z funkcji napisanych w Asemblerze
int nr_opcji=1, precyzja=1;
do
{
printf("=== Wybierz: ===\n");
printf("1 - aby sprawdzic aktualna precyzje obliczen\n");
printf("2 - aby zmienic aktualna precyzje obliczen:\n");
printf("0 - aby zakonczyc program\n");
scanf("%d", &nr_opcji);
switch(nr_opcji)
{
case 1:
printf("\nAktualna precyzja: ");
switch(sprawdz())
{
case 0: printf("Single Precision\n\n"); break;
case 2: printf("Double Precision\n\n"); break;
case 3: printf("Double Extended Precison\n\n");
break;
}
break;
case 2:
printf("\n1 - Single Precision\n");
printf("2 - Double Precision\n");
printf("3 - Double Extended Precision\n");
scanf("%d", &precyzja);
if (precyzja>3 || precyzja<1)
printf("Podano zla wartosc\n");
else ustaw(precyzja);
printf("\n");
break;
}
} while(nr_opcji!=0);
return 0;
}
Kod Asemblerowy:
.data
control_word: .short 0
.text
# Zadeklarowane tutaj funkcje będą możliwe do wykorzystania
# w języku C po zlinkowaniu plików wynikowych kompilacji obu kodów
.global ustaw, sprawdz
.type ustaw, @function
.type sprawdz, @function
ustaw:
push %rbp
mov %rsp, %rbp
# Pierwszy i jedyny parametr funkcji umieszczony zostanie
# w rejestrze RDI. Może on zawierać jedną z trzech wartości:
# 1 dla single, 2 dla double i 3 dla extended
# Pobranie zawartości rejestru kontrolnego do rejestru AX
# przez komórkę w pamięci
mov $0, %rax
fstcw control_word
fwait
mov control_word, %ax
# Wyzerowanie bitów kontroli prezycji (00 - początkowo single)
and $0xFCFF, %ax # 1111 1100 1111 1111
# Zamiana na double lub extended w zależności
# od przesłnego parametru
cmp $2, %rdi
jl koniec
je double
# Zmiania na extended (11)
extended:
xor $0x300, %ax # 0000 0011 0000 0000
jmp koniec # Aby nie zmienić na double
# Zmiana na double (10)
double:
xor $0x200, %ax # 0000 0010 0000 0000
koniec:
# Zapisanie słowa kontrolnego z rejestru AX do rejestru CW
mov %ax, control_word
fldcw control_word
mov %rbp, %rsp
pop %rbp
ret
sprawdz:
push %rbp
mov %rsp, %rbp
# Pobranie słowa kontrolnego FPU so rejestru AX
mov $0, %rax
fstcw control_word
fwait
mov control_word, %ax
# Wyzerowanie pozostałych bitów poza bitami kontroli precyzji
# i przesunięcie wygenerowanych bitów w prawo na koniec
and $0x300, %ax # 0000 0011 0000 0000
shr $8, %ax
# Wartość zwracana znajduje się już w EAX. Przyjmuje wartości:
# 0 dla single, 2 dla double i 3 dla extended
mov %rbp, %rsp
pop %rbp
ret
Zadanie drugie – Wyświetlanie i zmiana sposobu zaokrąglania
Kod C:
#include <stdio.h>
// Deklaracja funkcji które zostaną dołączone
// dopiero na etapie linkowania kodu
extern int sprawdz();
extern int ustaw(int tryb);
int main(void)
{
int nr_opcji=1, tryb=1;
do
{
printf("=== Wybierz: ===\n");
printf("1 - aby sprawdzic aktualny tryb zaokraglania\n");
printf("2 - aby zmienic aktualny tryb zaokraglania:\n");
printf("0 - aby zakonczyc program\n");
scanf("%d", &nr_opcji);
switch(nr_opcji)
{
case 1:
printf("\nAktualny tryb zaokraglania: ");
switch(sprawdz())
{
case 0: printf("Round to nearest\n\n"); break;
case 1: printf("Round down\n\n"); break;
case 2: printf("Round up\n\n"); break;
case 3: printf("Truncate\n\n"); break;
}
break;
case 2:
printf("\n0 - Round to nearest\n");
printf("1 - Round down\n");
printf("2 - Round up\n");
printf("3 - Truncate\n");
scanf("%d", &tryb);
if (tryb>3 || tryb<0) printf("Podano zla wartosc\n");
else ustaw(tryb);
printf("\n");
break;
}
} while(nr_opcji!=0);
return 0;
}
Kod Asemblerowy:
.data
control_word: .short 0
.text
# Zadeklarowane tutaj funkcje będą możliwe do wykorzystania
# w języku C po zlinkowaniu plików wynikowych kompilacji obu kodów
.global ustaw, sprawdz
.type ustaw, @function
.type sprawdz, @function
ustaw:
push %rbp
mov %rsp, %rbp
# Pierwszy i jedyny parametr funkcji umieszczony zostanie
# w rejestrze RDI. Jego możliwe wartości to:
# 0 dla nearest, 1 dla round down, 2 dla round up
# i 3 dla truncate
# Pobranie zawartości rejestru kontrolnego do rejestru AX
# przez komórkę w pamięci
mov $0, %rax
fstcw control_word
fwait
mov control_word, %ax
# Wyzerowanie bitów kontroli zaokrąglania
# (00 - początkowo nearest)
and $0xF3FF, %ax # 1111 0011 1111 1111
# Przesunięcie przesłanej jako parametr funkcji
# wartości w lewo o 10 bitów
shl $10, %rdi
# Ustawienie odpowiednich bitów w zawartości
# pobranego rejestru CW
xor %di, %ax
# Zapis zmienionej wartości do rejestru CW
mov %ax, control_word
fldcw control_word
mov %rbp, %rsp
pop %rbp
ret
sprawdz:
push %rbp
mov %rsp, %rbp
# Pobranie słowa kontrolnego FPU do rejestru AX
mov $0, %rax
fstcw control_word
fwait
mov control_word, %ax
# Wyzerowanie pozostałych bitów poza bitami kontroli
# zaokrąglania i przesunięcie wygenerowanych bitów
# w prawo na ostatnie pozycje
and $0xC00, %ax # 0000 1100 0000 0000
shr $10, %ax
# Wartość zwracana znajduje się już w EAX. Przyjmuje wartości:
# 0 dla nearest, 1 dla round down, 2 dla round up
# i 3 dla truncate
mov %rbp, %rsp
pop %rbp
ret
Szereg Taylora
Kolejnym zadaniem domowym był program przybliżający wartość funkcji sinus korzystając z rozwinięcia szeregu Taylora. Na zajęciach napisana została modyfikacja tego programu, tak aby przybliżana była wartość cosinusa. Oba programy są niemal identyczne.
Wartość funkcji sinus w zadanym punkcie przybliżana jest ze wzoru x -x3/3! +x6/6! ... +((-1)n * x2n-1)/(2n-1)!
. Program nie liczy w każdym obiegu pętli kolejnych wyrazów ciągu, a jedynie je uzupełnia. Pierwszym wyrazem ciągu jest x
– wartość kąta w radianach podana przez użytkownika. Następnym: poprzednia wartość ciągu * -x2 / (Y+1)(Y+2)
gdzie (Y+1)(Y+2)
to 2 kolejne “wyrazy” silni, przy założeniu że początkowo silnia jest równa 1.0. Dla drugiego wyrazu przyjmie ona wtedy wartość 1.0*2.0*3.0
, co daje 3!
. Przy obliczaniu wartości funkcji cosinus, początkowym wyrazem nie jest kąt podany przez użytkownika, a wartość 1.0. Silnie również zaczynamy liczyć od 0.0, tak aby dla pierwszego wyrazu przyjęła ona wartość 2!
, a nie 3!
(nie mnożymy wartości przez 0.0, pierwszym wyrazem jest (Y+1)(Y+2)
).
Zadanie trzecie – Aproksymacja funkcji Sinus
Kod C:
#include <stdio.h>
// Deklaracja funkcji która zostanie dołączona
// dopiero na etapie linkowania kodu
extern double taylor(double a, int b);
int main(void)
{
int i;
double f;
printf("Kat: ");
scanf("%lf", &f);
printf("Liczba wyrazow ciagu: ");
scanf("%d", &i);
printf("Twoj wynik to: %lf\n", taylor(f, i));
return 0;
}
Kod Asemblerowy:
.data
minus: .double -1.0
silnia: .double 1.0
.text
# Zadeklarowana tutaj funkcja będzie możliwa do wykorzystania
# w języku C po zlinkowaniu plików wynikowych kompilacji obu kodów
.global taylor
.type taylor, @function
# Funkcja obliczająca przybliżenie wartości sinusa
# z szeregu Taylora
taylor:
push %rbp
mov %rsp, %rbp
# Przyjmowane przez funkcje argumenty:
# RDI - ilość kroków
# XMM0 - kąt w radianach
# Załadowanie wartości z rejestru XMM0
# do rejestrów ST(0), ST(1) i ST(2) przez stos
sub $8, %rsp
movsd %xmm0, (%rsp)
fldl (%rsp) # ST(0) = przesłany kąt
fld %st # ST(1) = ST(0); ST(0) = zawartość ST(0)
# z poprzedniej linijki
fld %st # -||-
# Zawartość rejestrów FPU obecnie, na początku i końcu pętli:
# ST(0) - aktualny wyraz ciągu (początkowo równy x)
# ST(1) - aktualna suma ciągu (-||-)
# ST(2) - przesłany kąt (x)
movq $0, %rsi # Licznik do pętli
fwait # Oczekiwanie na zakończenie wykonywanych przez
# FPU obliczeń
petla:
# Wyskok z pętli po obliczeniu wszystkich wyrazów
cmp %rdi, %rsi
je koniec
inc %rsi # Zwiększenie licznika wykonań pętli
###
### Obliczenie licznika
###
fmul %st(2), %st # Aktualny wyraz ciągu (ST(0)) *= x
fmul %st(2), %st # Aktualny wyraz ciągu (ST(0)) *= x
fmull minus # Aktualny wyraz ciągu (ST(0)) * (-1)
# Po wykonaniu tych instrukcji uzyskamy:
# Aktualny wyraz ciągu = poprzedni wyraz ciągu * (-x^2)
###
### Obliczanie mianownika - silni
###
fld1 # ST(0) -> ST(1)...; ST(0) = 1.0
fld1 # ST(0) -> ST(1)...; ST(0) = 1.0
fldl silnia # ST(0) -> ST(1)...;
# ST(0) = zawartość komórki pamięci - silnia
# Aktualne rejestry:
# ST(0) - numer wyrazu silni (z "silnia")
# ST(1) - wynik silni (bez uwzględnienia
# wcześniejszych części), początkowo 1.0
# ST(2) - 1.0
# Obliczenie kolejnych dwóch wyrazów silni:
# Jeśli początkowa wartość ze zmiennej "silnia" była równa
# Y, to w tej części obliczane są "wyrazy" (Y+1)*(Y+2).
# Przez obliczoną wartość dzielny jest poprzedni wyraz
# ciągu, po uprzednim pomnożeniu przez -x^2.
# Po tym etapie, obecny wyraz ciągu jest równy:
# (poprzedni wyraz ciągu * -x^2) / (Y+1)*(Y+2)
fadd %st(2), %st
fmul %st, %st(1)
fadd %st(2), %st
fmul %st, %st(1)
fstpl silnia # Zapisanie numery ostatniego wyrazu
# silni do zmiennej i ściągnięcie go
# ze "stosu" FPU
# Usunięcie niepotrzebnych wartości
fxch %st(1) # Zamiana miejscami ST(0) i ST(1)
fstp %st # Ściągnięcie ze "stosu" FPU ostatniej
# wartości
# Aktualne rejestry:
# ST(0) - silnia (obecny dzielnik)
# ST(1) - wartość do podzielenia (aktualny wyraz ciągu)
# ST(2) - aktualna suma ciągu
# ST(3) - przesłany kąt (x)
fdivr %st, %st(1) # Dzielenie obecnego wyrazu przez
# dzielnik (silnie) - wynik trafi
# do ST(1)
fstp %st # Usunięcie obecnego dzielnika ze "stosu" FPU
# Zawartość rejestrów jak na początku pętli
fadd %st, %st(1) # Dodanie wartości obecnego wyrazu
# do wyniku globalnego
jmp petla # Powrót na początek pętli
# Przeniesienie wyniku z rejestru ST(0) (sumy ciągu)
# do rejestru XMM0 przez stos i zakończenie funkcji
koniec:
fstp %st
fstpl (%rsp)
movsd (%rsp), %xmm0
mov %rbp, %rsp
pop %rbp
ret
Zadanie czwarte – Aproksymacja funkcji Cosinus
Kod C:
#include <stdio.h>
// Deklaracja funkcji która zostanie dołączona
// dopiero na etapie linkowania kodu
extern double taylor(double a, int b);
int main(void)
{
int i;
double f;
printf("Kat: ");
scanf("%lf", &f);
printf("Liczba wyrazow ciagu: ");
scanf("%d", &i);
printf("Twoj wynik to: %lf\n", taylor(f, i));
return 0;
}
Kod Asemblerowy:
.data
minus: .double -1.0
silnia: .double 0.0
.text
# Zadeklarowana tutaj funkcja będzie możliwa do wykorzystania
# w języku C po zlinkowaniu plików wynikowych kompilacji obu kodów
.global taylor
.type taylor, @function
# Funkcja obliczająca przybliżenie wartości sinusa
# z szeregu Taylora
taylor:
push %rbp
mov %rsp, %rbp
# Przyjmowane przez funkcje argumenty:
# RDI - ilość kroków
# XMM0 - kąt w radianach
# Załadowanie wartości z rejestru XMM0
# do rejestrów ST(0), ST(1) i ST(2) przez stos
sub $8, %rsp
movsd %xmm0, (%rsp)
fldl (%rsp) # ST(0) = przesłany kąt
fl1 # ST(1) = ST(0); ST(0) = 1.0
fl1 # -||-
# Zawartość rejestrów FPU obecnie, na początku i końcu pętli:
# ST(0) - aktualny wyraz ciągu (początkowo 1.0)
# ST(1) - aktualna suma ciągu (początkowo 1.0)
# ST(2) - przesłany kąt (x)
movq $0, %rsi # Licznik do pętli
fwait # Oczekiwanie na zakończenie wykonywanych
# przez FPU obliczeń
petla:
# Wyskok z pętli po obliczeniu wszystkich wyrazów
cmp %rdi, %rsi
je koniec
inc %rsi # Zwiększenie licznika wykonań pętli
###
### Obliczenie licznika
###
fmul %st(2), %st # Aktualny wyraz ciągu (ST(0)) *= x
fmul %st(2), %st # Aktualny wyraz ciągu (ST(0)) *= x
fmull minus # Aktualny wyraz ciągu (ST(0)) * (-1)
# Obecny wyraz ciągu = poprzedni wyraz ciągu * (-x^2)
###
### Obliczanie mianownika - silni
###
fld1 # ST(0) -> ST(1)...; ST(0) = 1.0
fld1 # ST(0) -> ST(1)...; ST(0) = 1.0
fldl silnia # ST(0) -> ST(1)...;
# ST(0) = zawartość komórki pamięci - silnia
# Aktualne rejestry:
# ST(0) - numer wyrazu silni (z "silnia")
# ST(1) - wynik silni (bez uwzględnienia
# wcześniejszych części), początkowo 1.0
# ST(2) - 1.0
# Obliczenie kolejnych dwóch wyrazów silni:
# Jeśli początkowa wartość ze zmiennej "silnia" była równa
# Y, to w tej części obliczane są "wyrazy" (Y+1)*(Y+2).
# Przez obliczoną wartość dzielny jest poprzedni wyraz
# ciągu, po uprzednim pomnożeniu przez -x^2.
# Na tym etapie, obecny wyraz ciągu jest równy:
# (poprzedni wyraz ciągu * -x^2) / (Y+1)*(Y+2)
fadd %st(2), %st
fmul %st, %st(1)
fadd %st(2), %st
fmul %st, %st(1)
fstpl silnia # Zapisanie numery ostatniego wyrazu silni
# do zmiennej i ściągnięcie go
# ze "stosu" FPU
# Usunięcie niepotrzebnych wartości
fxch %st(1) # Zamiana miejscami ST(0) i ST(1)
fstp %st # Ściągnięcie ze "stosu" FPU
# ostatniej wartości
# Aktualne rejestry:
# ST(0) - silnia (obecny dzielnik)
# ST(1) - wartość do podzielenia (aktualny wyraz ciągu)
# ST(2) - aktualna suma ciągu
# ST(3) - przesłany kąt (x)
fdivr %st, %st(1) # Dzielenie obecnego wyrazu przez
# dzielnik (silnie) - wynik trafi
# do ST(1)
fstp %st # Usunięcie obecnego dzielnika ze "stosu" FPU
# Zawartość rejestrów jak na początku pętli
fadd %st, %st(1) # Dodanie wartości obecnego wyrazu
# do wyniku globalnego
jmp petla # Powrót na początek pętli
# Przeniesienie wyniku z rejestru ST(0) (sumy ciągu)
# do rejestru XMM0 przez stos i zakończenie funkcji
koniec:
fstp %st
fstpl (%rsp)
movsd (%rsp), %xmm0
mov %rbp, %rsp
pop %rbp
ret
Specjalne podziękowania dla Mateusza Gniewkowskiego za pomoc w przygotowaniu tego wpisu :-)
Dobra robota ;)
przepraszam ze tutaj komentuje, ale nie znalazlem innej drogi
czy moglbys na nadajniki.olo.ovh zrobic jakas mozliwosc filtrowania po firmie? filtr pozytywny i negatywny (np. zeby mozna bylo odfiltrowac lokalna energetyke ktora ma wiekszosc infrastruktury w okolicach wiejskich i zostawic wszystko inne)
niestety nie jest to możliwe, ale zapraszam na http://http://uke.kurylowicz.info/, to chyba to czego szukasz. moja mapa jest generowana w google fusion tables, jako warstwa mapy google. wykonałem to w ten sposób, ponieważ punktów jest na prawdę dużo, a chcę wyświetlić je wszystkie. jeśli miały by one być nanoszone na mapę w skrypcie js, co pozwoliło by na ich filtrowanie, załadowanie mapy po wejściu na stronę trwało by wieki.
<3
Jak wyłączyć tę zgode na kopanie kryptowaluty? Telefon ledwo chodzi…
Proszę wyczyścić ciasteczka, tam zapisana jest zgoda. Coś musiało pójść nie tak. Czy mogę prosić o informacje jaki to telefon? Kopanie jest zablokowane na telefonach, a na komputerach nie powinno zużyć więcej jak 25% mocy CPU.