Matematyka w C#

0

Cześć,
Mam kod:

    private void number_TextChanged(object sender, EventArgs e){
            var value = Regex.Replace(number.Text, "[^0-9]", "");
            number.Text = value;
            int count = value.Count();
            string completeValue = "";
            for(int i = 0; i < 8 - count; i++){
                completeValue = completeValue + "0";
            }
            completeValue = completeValue + value;
            int sum = 0;
            MessageBox.Show(sum.ToString());
            for (int i = 0; i < 8; i++){
                switch(i + 1 % 3){
                    case 0 :
                        sum += 1 * Convert.ToInt32(completeValue[i]); 
                        break;
                    case 1:
                        sum += 3 * Convert.ToInt32(completeValue[i]);
                        break;
                    case 2:
                        sum += 7 * Convert.ToInt32(completeValue[i]);
                        break;
                }
            }
            MessageBox.Show(sum.ToString());
        }

W pierwszym MessageBox'sie mam wartość takiej jak się spodziewałem czyli 0. Jednakże przy jakimkolwiek ciągu (nawet 00000000) zamiast 0 (z tego co się uczyłem 0 * 10000 = 0) dostaję 480. Taką wartość dostaję przy każdym ciągu (00000001, 00000002, itd.). Mógłby mi ktoś wyjaśnić czemu i co zrobić aby wyświetlała się poprawna wartość?
Dodam że zmienna completValue zawsze zawiera 8 znakowy string.

0

Powiedz może, co według Ciebie ten kod robi, bo powiem szczerze, że nie ogarniam - za dużo magicznych numerków i w ogóle magii.

0

Najpierw pobiera to co wprowadził user. Następnie wywala wszystko co nie jest cyfrą i wstawia to do inputa. Następnie tworzy 8 znakowy string (zera na poczatku, następnie cyfry od usera). Następnie z otrzymanego, 8 elementowego ciągu znaków każdy przerabia na int i ma przemnożyć przez odpowiednią cyfrę (zależną od wyniku dzielenia modulo przez 3). Następnie ma zwrócić sumę. Potem będzie to wszystko ładnie rozpisane po metodach.

0

Jak sobie to ładnie rozpiszesz, to problem zniknie prawdopodobnie - w tym forze coś nie pomieszałeś? Poza tym sprawdź debuggerem.

0

weź dodawanie przed modulo w nawias ;)

3

A po co w ogóle zmienna count, skoro value.Length jest tysiąc razy lepsze, bo kompilator zoptymalizuje i nie będzie robił zbędnego sprawdzania zakresów tablic?

0

Zamiast Convert.ToInt32() użyj int.Parse + tak jak napisał topik92 dodawanie w i + 1 % 3 obejmij nawiasami.
Twój problem bierze się z tego, że nie umiesz używać debugera (polecam lekturę Debugowanie), nie wiesz, że 48 to kod ascii zera i nie znasz kolejności wykonywania działań - najpierw dzielenie, potem dodawanie. W efekcie pierwsza iteracja pętli wykonuje case 1 z Convert zwracajacym 48, druga iteracja - case 2, kolejne iteracje nigdy się nie wykonają, bo w wyniku zjedzenia nawiasu i + 1 % 3 > 2, więc masz (3 + 7) * 48.

@somekind - możesz podesłać jakiś link co do tej optymalizacji? Nie słyszałem o tym i chętnie poczytam.

0

@ŁF
https://social.msdn.microsoft.com/Forums/en-US/d897034c-380a-4a60-9539-1a1ae64ee951/arraylength-vs-arraycount?forum=csharpgeneral
no i jeszcze mozesz sobie napisac dwie petle i zobaczyc IL code (wystarczy uzyc IL Disassembler)

1

@somekind, @fasadin

			var array = new int[100000000];
			var sw = new Stopwatch();

			// tu wstępna rozgrzewka, żeby zminimalizować wpływ kolejności wykonywania pętli, bez tego fragmentu pierwsza pętla wykonuje się prawie dwa razy dłużej
			for (int i = 0; i < array.Length; i++) DoSomething(array[i]);

// var
			sw.Restart();
			var l = array.Length;
			for (int i = 0; i < l; i++) DoSomething(array[i]);
			Console.WriteLine(sw.Elapsed);
// inline
			sw.Restart();
			for (int i = 0; i < array.Length; i++) DoSomething(array[i]);
			Console.WriteLine(sw.Elapsed);
// count
			sw.Restart();
			for (int i = 0; i < array.Count(); i++) DoSomething(array[i]);
			Console.WriteLine(sw.Elapsed);

Wynik nie potwierdza tego, że Length inline będzie szybszy. .NET 4.5.1, x64, w zależności od kolejności sposobu var i inline:
release z rozgrzewką, kolejność v-i-c:
00:00:00.2081505 - var 00:00:00.2020022 - inline 00:00:04.3296516 - count - jak widać wersja z Count() jest najwolniejsza

release z rozgrzewką, kolejność i-v-c:
00:00:00.2010472 - inline 00:00:00.2028995 - var 00:00:04.4164176 - count

release z rozgrzewką, kolejność c-i-v:
00:00:04.3482894 - count 00:00:00.2038560 - inline 00:00:00.1712872 - var

release z rozgrzewką, kolejność c-v-i:
00:00:04.3437966 - count 00:00:00.2023574 - var - szybciej niż inline w poprzednim przypadku (!) 00:00:00.1818407 - inline - wolniej niż var w poprzednim przypadku (!)

Jeszcze jako ciekawostka:
debug z rozgrzewką, kolejność c-v-i:
00:00:04.6194327 - count - minimalny wpływ przełączenia z release na debug 00:00:00.4090494 - var - około dwa razy dłużej niż release, ALE tym razem drugi algorytm zawsze wygrywa z trzecim, czyli w debug użycie zmiennej jest szybsze od odwołania do property 00:00:00.4399661 - inline

release bez rozgrzewki
00:00:00.2747313 - var 00:00:00.1765430 - inline
i na odwrót
00:00:00.2746089 - inline 00:00:00.1709781 - var

debug z unchecked (słowo kluczowe unchecked) i rozgrzewką
00:00:00.2498583 - inline 00:00:00.2449227 - var

release z unchecked i rozgrzewką
00:00:00.2415720 - inline 00:00:00.2481471 - var

j/w, odwrotna kolejność:
00:00:00.2435900 - var - tu odwrotny wniosek, var jest o kilka promili wolniejszy 00:00:00.2499914 - inline - a tu z kolei też wolniej

Wnioski: wpływ kolejności wykonywania pętli jest zadziwiająco duży, im później algorytm się wykonuje tym ma większe fory, a przecież tablica ma 400MB, więc cache L1-L3 nie powinien mieć nic do tego. Z tego powodu tyle testów.
Porównując czasy wykonania algorytmów wykonywanych na tych samych miejscach wersja z użyciem dodatkowej zmiennej (nazwałem ją sobie "var") jest zwykle minimalnie (0-5%) szybsza od użycia odwołanie do pola bezpośrednio w pętli ("count"). Czyli albo boundary check nie ma nic do rzeczy, albo mój kompilator ssie (ale w release build ma włączoną optymalizację). Użycie metody Count() zwiększa czas wykonania o rząd wielkości (!), jak widać nie ma tu żadnej optymalizacji i metoda jest wołana przy każdej iteracji.
Widać też, na czym polega w tym konkretnym przypadku optymalizacja kompilatora przy buildzie dla release - wyłączany jest boundary check.
Trzeba wziąć pod uwagę, że sama pętla zwykle w swoim ciele ma zawartą operację znacznie bardziej czasochłonną od samej pętli, wtedy to, co jest zawarte w warunku zakończenia pętli jest drugorzędne.

Zrobiłem jeszcze mały test dla starszych .net i oczy mi się szeroko otworzyły:
2.0: 0000.0720307 - ponad trzy razy szybciej - optymalizator wyciął wywołanie DoSomething
3.0: 0000.0703629 - j/w
3.5: 0000.0647380 - j/w
3.5 CP: 0000.0600336 - (!) to już cztery razy szybciej
4.0: 0000.2273934 (prawie 10% szybciej bez wycinania DoSomething)

4.6.1 dla x86: 0000.1941212
4.6.1 dla x64: 0000.2357737
4.6.1 dla any cpu: wyniki jak dla x64

Na koniec jeszcze pstryczek w nos @_13th_Dragon: wersja z preinkrementacją w pętlach (release, unchecked z rozgrzewką) - jak widać czasy są takie same, jak w postinkrementacji:
00:00:00.2463280 - inline 00:00:00.2499132 - var

0

@ŁF tu chodzi o count vs length. Czy to czy jest inline czy nie to nie powinno miec znaczenia (i jak widac za duzego znaczenia nie ma). I tak jak wspominamy z @somekind count jest wolniejsze

A jezeli chodzi o rozgrzewke to gdy wywolujesz kod w C# to ten jest generowany na podstawie IL code. Pozniej kazde wywolanie danej funkcji jest juz trzymane w pamieci.

Inrementacje (post i pre) nie maja znaczenia w C#, zawsze beda wykonywac sie bardzo podobnie (nie jestem pewien tej informacji, nie pamietam jakie zrodlo to bylo, ale jak trzeba to moge poszukac).

0
fasadin napisał(a):

@ŁF tu chodzi o count vs length

Nie sądzę: "a po co w ogóle zmienna count, skoro value.Length jest tysiąc razy lepsze".

fasadin napisał(a):

A jezeli chodzi o rozgrzewke to gdy wywolujesz kod w C# to ten jest generowany na podstawie IL code.

Chyba na odwrót. Kompilacja JIT do kodu natywnego niewiele ma tu do rzeczy, bo pojedyncze wywołanie DoSomething nic nie zmienia.

0

ale on uzywa

int count = value.Count();

a nie value.Length

0

Przecież to nie ma znaczenia oO. Wartość w zmiennej to wartość, optymalizator tego nie łyka. Na wszelki wypadek sprawdziłem, może optymalizator jest cwańszy ode mnie, ale nie, czas iterowania jest praktycznie taki sam.

0

Ja zrozumialem somekinda inaczej (stad tez moj link), myslalem ze jemu chodzi o czyste
length vs count
Pomijajac zupelnie var. Sorry za zamieszanie

var nie powinien dac znaczacej przewagi

1

@ŁF, @fasadin, być może moja wiedza (zaczerpnięta z podlinkowanego artykułu jest przestarzała) i kompilator jest już mądrzejszy niż te kilka lat temu, i w przypadku takiej tymczasowej lokalnej zmiennej też potrafi zrezygnować z ABC. @msm, dodasz coś na ten temat?

Niemniej jednak pewne jest, że istniały kompilatory/jittery/CLR, które tego nie robiło, więc profilaktycznie warto trzymać się tej zasady. Zwłaszcza, że dodatkowa zmienna jest po prostu mniej czytelna niż odwołanie się bezpośrednio do a.Length. W przypadku dodatkowej zmiennej trzeba jej szukać w kodzie, sprawdzić co do nie jej jest wstawiane i zastanowić się, czemu autor zrobił tak, zamiast po ludzku.

2
somekind napisał(a):

A po co w ogóle zmienna count, skoro value.Length jest tysiąc razy lepsze, bo kompilator zoptymalizuje i nie będzie robił zbędnego sprawdzania zakresów tablic?
array.Length vs zmienna - skompilowalam pod najnowszym vc# przy wlaczonych optymalizacjach i nie sprawdzaja zakresu, dodatkowo kod wygenerowany przez jit jest praktycznie identyczny (troche inna kolejnosc instrukcji itp).
Count() jest wywolywane za kazdym razem i stad roznica

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