Czemu mnożenie 1/2*a zawsze zwraca 0?

0

Czemu taki program zwraca zawsze 0, a gdy zamieni się miejscami 1/2 ze zmienną a to otrzymujemy poprawny wynik?

#include <iostream>

using namespace std;

int main()
{
    float ciekawa_l, a=2;

    ciekawa_l=1/2*a;

    std::cout<<"Ciekawa liczba wynosi: "<<ciekawa_l;


    return 0;
}
3

Bo literały liczbowe są domyślnie typu int tak więc rezultat po prawej stronie ciekawa_l=1/2*a; będzie domyślnie typu int. Określ przynajmniej jeden z literałów w równaniu jako zmiennoprzecinkowy to dostaniesz wynik jakiego się spodziewasz. Poniżej zrobiłem to dla obu.

ciekawa_l= 1.0 / 2.0 * a;

0

Bo nawiasy dajesz tam gdzie trzeba

2

Albo mnóż od razu przez 0,5.

#include <iostream>

using namespace std;

int main()
{
    float ciekawa_l, a=2;

    ciekawa_l=0.5*a;

    std::cout<<"Ciekawa liczba wynosi: "<<ciekawa_l;


    return 0;
}
0
several napisał(a):

Bo literały liczbowe są domyślnie typu int tak więc rezultat po prawej stronie ciekawa_l=1/2*a; będzie domyślnie typu int. Określ przynajmniej jeden z literałów w równaniu jako zmiennoprzecinkowy to dostaniesz wynik jakiego się spodziewasz. Poniżej zrobiłem to dla obu.

ciekawa_l= 1.0 / 2.0 * a;

Okej, ale dlaczego jeżeli zamienimy to na ciekawa_l=a*1/2;, to otrzymujemy prawidłowy wynik? Dlatego, że na początku mnożymy przez zmienną "a", która jest zmiennoprzecinkowa?

5

@Bojomir

Okej, ale dlaczego jeżeli zamienimy to na ciekawa_l=a*1/2;, to otrzymujemy prawidłowy wynik? Dlatego, że na początku mnożymy przez zmienną "a", która jest zmiennoprzecinkowa?

I tak i nie. Mea culpa, teraz widzę, że odpowiedziałem nieprecyzyjnie.

Typy elementów w wyrażeniu arytmetmycznym są promowane niejawnie do większego typu. Także jeśłi masz a / 2 lub 2 / a literał będący typem int będzie promowany niejawnie do typu float bo a jest tego typu. I nie ważne czy a jest pierwsze czy drugie.

Z tym, że wyrażenia są ewaluowane od lewej do prawej zgodnie z kolejnością arytmetyczną, także Twoje oryginalne działanie 1 / 2 * a będzie rozłożone na dwa etapy - 1 / 2 a potem rezultat tegoż mnożone przez a. W pierwszym etapie dzielisz typ int przez typ int i rezultat będziesz mieć typu int skoro obydwa składowe są tego typu, a jako, że nie obsługuje on wartości zmiennoprzecinkowych to jeśli rezultat będzie poniżej jeden to będziesz mieć zero. Potem to zero dzielisz przez a a więc zawsze dostaniesz zero i nie ma znaczenia czy to pierwsze zero będzie promowane z int do float.

Także a nie musi być całkowicie pierwsze w całym wyrażeniu, musi być po prostu ewaluowane jako pierwsze, żeby podnieść całą prawą stronę do float. Czyli 1 * a / 2 również powinno zwracać poprawny wynik.

0

@Bojomir

Okej, ale dlaczego jeżeli zamienimy to na ciekawa_l=a*1/2;, to otrzymujemy prawidłowy wynik? Dlatego, że na początku mnożymy przez zmienną "a", która jest zmiennoprzecinkowa?

Nie, nawet jakby nie była zmiennoprzecinkowa to kolejność działań obowiązuje i masz najpierw a*1 czyli 2 a potem 2/2 czyli 1. W 1/2*a masz najpierw 1/2 czyli 0 a potem 0*a czyli zawsze 0

0

Kompilator generuje takiego assemblera, że traktuje jako consta 1/2 pierwsze wyrażenie i zaokrągla w dół, czyli masz 1/2, to 0.5 zaokraglone w dół przy castowaniu na inta czyli wartość 0 w tym przypadku i zapisuje tą wartość w .rodata, jest to wykonane w czasie kompilacji.
To oznacza, że jak uruchomisz program to już nie jest liczona ta stała.

Jak by to był float to w czasie kompilacji kompilator w .rodata ustawiłby wartość floata jako stałą.
Czyli zamiast literałów intowskich 1, 2, 3. to trzeba floatowe 1.0, 2.5, 3.0.
Wtedy stała odłożona do .rodata będzie floatem, gdyż uda wtedy się kompilatorowi typ wydedukować na podstawie literału.

A jako, że jest zero, to w tym przypadku kompilator pxor xmm0, xmm0 xoruje rejestr xmm0, robi to tak bo jest to prostsze niż wczytywanie z pamięć wartości zero.
Jeśli będzie jakaś stała to zamiast xorować, to wczytuje ją movss xmm0, DWORD PTR .LC0, z .rodata
Potem mnoży mulss xmm0, xmm1, gdzie
I zapisujemy wynik do zmiennej na stosie.
Ale to w przypadku jeśli 1/2 policzył jako stałą, jeśli a*1 policzył jako stałą to będzie
dzielenie divss xmm0, xmm1

Jak zamienisz miejscami 1/2*a , na a*1/2 to kompilator, nie liczy tej stałej przy kompilacji gdyż nie może wykonać a/2 dzielenia(może w ekstremalnej optymalizacji od razu wynik w .rodata zapisać jak stwierdzi, że nigdzie nie jest zmienna modyfikowana), gdyż jest to tożsame z zapisem 1*a/2 lub a*1/2, jest to równoznaczny zapis matematyczny po skróceniu.
W takim wypadku kompilator odkłada jako stałą 2 w .rodata i wykonuje dzielenie bo inaczej nie da się tego rozwiązać.

W jednym wypadku liczysz 1/2 = stała i potem w uruchomionym programie stała * a, a w drugim a*1 = stała i w dynamicznie wyliczone stała / 2

1 użytkowników online, w tym zalogowanych: 0, gości: 1