Math.Round, double, dokładność do N miejsc po przecinku, MSTest

Math.Round, double, dokładność do N miejsc po przecinku, MSTest
VarrComodoo
  • Rejestracja:około 14 lat
  • Ostatnio:dzień
  • Lokalizacja:Bk
  • Postów:480
0

Trochę się zakręciłem w problemie i potrzebuje czyjegoś spojrzenia z zewnątrz, będę wdzięczny za podpowiedzi.

Opis Problemu:
Pobieram z pliku tekstowego wartość czasu wyrażonego dziesiętnie np. '0,25', '7,67', '0,33333', '0,044'. Taka postać tekstowa tej wartości czasu, ma różną liczbę cyfr po przecinku, może mieć dokładność od 0 do 5 miejsc po przecinku, w zależności od tego co to za czas przygotowania/realizacji/nakładania/itp.
Ale jest tak że np. czas realizacji w całym systemie może być reprezentowany przez 'double' z dokładnością do 5ciu miejsc po przecinku a w niektórych przypadkach w pliku tekstowym z którego importuje dane, ma dokładność 2 miejsc po przecinku, i to jest ok, czasami tak bywa np. 15min to '0,25'. (Nie mam wpływu na plik tekstowy i nie uzyskam tam wartości '0,25000' dla 15minut.)

Następnie konwertuję taki czas na 'double' w obiekcie który czyta ten plik i w tym obiekcie buduję swój obiekt 'CzasScala' do którego konstruktora podaje już 'double' a nie 'string' (potrzebuje takiego obiektu do obliczeń, edycji i wyswietlania tego czasu w różnych formatach w programie).

Kopiuj
    public class CzasScala : IComparable<CzasScala>
    {
        public int Dokladnosc_CzasDec_PoPrzecinku { get; private set; }
       
        public CzasScala(double d, int dokladnoscPoPrzecinku)
        {
            Dokladnosc_CzasDec_PoPrzecinku = dokladnoscPoPrzecinku;
            UstawNaPodstawieDziesietnej(d);
        }
        public double GodzinDziesietnie
        {
            get { return _godzinDziesietnie; }
            set { UstawNaPodstawieDziesietnej(value); }
        }
        private double _godzinDziesietnie;

        public int SumaSekund
        {
            get { return _sumaSekund; }
            set { UstawNaPodstawieSumySekund(value); }
        }
        private int _sumaSekund;

        public TimeSpan CzasStandard
        {
            get { return _czasStandard; }
            set { UstawNaPodstawieDateTime(value); }
        }
        private TimeSpan _czasStandard;

        private void UstawNaPodstawieDziesietnej(double d)
        {
            _sumaSekund = WyliczSekundy(d);
            WyliczSkladoweZSumySekund(_sumaSekund);
        }

        private int WyliczSekundy(double d)
        {
            return (int)(d * 3600d);
        }
        private void WyliczSkladoweZSumySekund(int s)
        {
            _czasH = (_sumaSekund / 3600);
            _czasM = (_sumaSekund % 3600 / 60);
            _czasS = (_sumaSekund % 3600 % 60);

            _godzinDziesietnie = Math.Round((double)s / 3600d, Dokladnosc_CzasDec_PoPrzecinku);
            _czasStandard = new TimeSpan(_czasH, _czasM, _czasS);
        }

//pozostala czesc klasy

I teraz problem, jeżeli opakuje taki czas realizacji '0,044' w 'CzasScala' i przeprowadzę na nim jakieś obliczenia lub edycje do postaci np. '0,3784' to dalej w programie wyeksportuje go do pliku tekstowego w takiej właśnie formie '0,3784' i to jest ok, tak ma być. Ale jeżeli pobiorę czas realizacji w formie '0,044' i nic z nim nie zrobię w programie to chciałbym go wyeksportować w postaci identycznej jak pobrałem czyli '0,044' a teraz wg tego co robi mój program, nie ruszony czas zapisuje mi w formie '0,04389' a tak nie chce.

Wiem ze problem leży w metodzie 'WyliczSkladoweZSumySekund' i doszedłem w 'Consoli' jak uzyskać to co chce, po prostu ustawiając właściwość w klasie musze ją ustawiać z dokładnością taką z jaką przychodzi z tekstu, ale jedyny sposób jaki wymyśliłem żeby pobierać dokładność po przecinku wartości która przychodzi to Split(',') i pobranie długości ostatniego stringa z uzyskanej tablicy stringów - ale to mi się wydaje za grubo. Jest jakiś inny rozsądny sposób aby uzyskać efekt o jaki mi chodzi? Może cos zamiast 'Math.Round'

Kopiuj
 static void Main(string[] args)
        {
            //odczytana, docelowaDokladnoscPoPrzecinku, sumaSekund, Gdzin, Minut,Sekund, CultureInfo
            //[DataRow(15.35  , 2, 55260, 15, 21, 0, "en-US")]
            //[DataRow(0.044  , 4, 158  , 0, 2, 38, "en-US")]
            //[DataRow(0.064  , 2, 230  , 0, 3, 50, "en-US")]
            //[DataRow(0.0645 , 2, 232  , 0, 3, 52, "en-US")]
            CultureInfo.CurrentCulture = new CultureInfo("en-US", false);

            double d = 0.044;

            int sekund = 158;
            int dokladnoscDocelowa = 5;


            double sekundDouble = (double)sekund;
            double tymczasowa = Math.Round(sekundDouble / 3600d, 3);   //tu na sztywno podaje dokladnosc 3 miejsc po przecinku (bo 0.044)
            double _godzinDziesietnie = Math.Round(tymczasowa, dokladnosc);

            Console.WriteLine($"Przypadek dla {d}");
            Console.WriteLine();
            Console.WriteLine($"Sekund = '{sekund}'\t\n" +
                $"dokladnosc: '{dokladnosc}' miejsc po przecinku, \t\n" +
                $"wartość uzyskana Double = '{_godzinDziesietnie}'");
            Console.ReadLine();
        }

Problem uwidocznił się w programie i dlatego żeby zacząć go rozwiązywać zacząłem tworzyć testy jednostkowe.

Kopiuj
        // doubleH_IN, poPrzecinkuIN, SUMAsekundOUT, godzinOUT, minutOUT , sekundOUT, culture
        [DataTestMethod]
        [DataRow(3.5,       2,  12600,  3,  30, 0,  "en-US")]
        [DataRow(0.25,      2,  900,    0,  15, 0,  "en-US")]
        [DataRow(0.17,      4,  612,    0,  10, 12, "en-US")]
        [DataRow(15.35,     2,  55260,  15, 21, 0,  "en-US")]
        [DataRow(0.044,     5,  158,    0,  2,  38, "en-US")]
        [DataRow(0.064,     2,  230,    0,  3,  50, "en-US")]
        [DataRow(0.0645,    2,  232,    0,  3,  52, "en-US")]
        [DataRow(0.33333,   5,  1200,   0,  20, 0,  "en-US")]
        [DataRow(0.66666,   2,  2400,   0,  40, 0,  "en-US")]
        [DataRow(0.66,      1,  2376,   0,  39, 36, "en-US")]
        [DataRow(0.67,      1,  2412,   0,  40, 12, "en-US")]
        [DataRow(0.00001,   0,  0,      0,   0,  0, "en-US")]
        public void TestKonstruktora_DoubleInt_CultureEN_TestUstawianiaWszsytkichPol(double d, int i,
            int sumaSekundResult, int godzinResult, int minutResult, int sekundResult, string cultureInfo)
        {
            CultureInfo.CurrentCulture = new CultureInfo(cultureInfo, false);

            CzasScala czas = new CzasScala(d, i);
            TimeSpan tsResult = new TimeSpan(godzinResult, minutResult, sekundResult);

            Assert.AreEqual(sumaSekundResult, czas.SumaSekund, $"blad w 'SumaSekund' spodziewalem sie {sumaSekundResult} a otrzymalem {czas.SumaSekund}");
            Assert.AreEqual(d, czas.GodzinDziesietnie, $"blad w 'GodzinaDziesietnie' spodziewalem sie {d} a otrzymalem {czas.GodzinDziesietnie}");
            Assert.AreEqual(tsResult, czas.CzasStandard, $"blad w 'TimeSpan', spodziewalem sie {tsResult} a otrzymalem {czas.CzasStandard}");
        }

Sterczące kolce Pondijusa, ostre grzebienie Daktyloskopei, Trygla i latający Wieprzoryb są niczym wobec Bestii która nas gnębi...
edytowany 1x, ostatnio: VarrComodoo
Spine
  • Rejestracja:około 22 lata
  • Ostatnio:minuta
  • Postów:6651
2

Napisz sobie klasę do ułamków zwykłych. Jak użyjesz w niej BigInteger, to masz praktycznie nieograniczoną precyzję i odporność na wewnętrzną reprezentację liczb zmiennoprzecinkowych w komputerach.


🕹️⌨️🖥️🖱️🎮
edytowany 2x, ostatnio: Spine
VarrComodoo
Ale mi nie chodzi o jak najwieksza dokladnosc
Spine
Ale jeżeli pobiorę czas realizacji w formie '0,044' i nic z nim nie zrobię w programie to chciałbym go wyeksportować w postaci identycznej jak pobrałem czyli '0,044' a teraz wg tego co robi mój program, nie ruszony czas zapisuje mi w formie '0,04389' a tak nie chce. - Twoja klasa do ułamków odczyta 0,044 jako 44/1000 i zapisze 44/1000 skonwertowane do ułamka dziesiętnego. Nie tracisz precyzji, więc osiągasz zamierzony efekt.
VarrComodoo
Faktycznie, nie pomyslalem o tym zaraz sprobuje
AK
  • Rejestracja:ponad 6 lat
  • Ostatnio:19 dni
  • Postów:3561
1

Zakładam, masz ugruntowane liczby zmiennoprzecinkowe (choćby na poziomie wikipedii), i wiesz, ze mają dwa problemy:

  • dokładności
  • reprezentacji

Bo C to najlepszy język, każdy uczeń ci to powie
VarrComodoo
generalnie z grubsza wiem o tym zaokraglaniu 'niekontrolowanym' chyba z powodu systemu dwojkowego ale wydawalo mi sie ze do moich zastosowan bedzie wystarczajacy. Sugerujesz ze lepiej w tym miejscu uzyc decimal?
VarrComodoo
miałeś racje, w dalszej części testów dogrzebałem się do błędu na kilkunastym miejscu po przecinku. zmieniłem na 'decimal' i jak ręką uciął
VarrComodoo
  • Rejestracja:około 14 lat
  • Ostatnio:dzień
  • Lokalizacja:Bk
  • Postów:480
0

na typie decimal mam ten sam problem:

Kopiuj
            decimal oczekiwana = 0.044m;
            decimal zmiennaD = 158;
            decimal wynik = zmiennaD / 3600m;
            decimal docelowaDoPieciuMiejscPoPrzecinku = Math.Round(wynik, dokladnoscDocelowa);

            Console.WriteLine($"oczekiwane {oczekiwana}, na wyjsciu '{docelowaDoPieciuMiejscPoPrzecinku}'");

wynik:

oczekiwane 0.044, na wyjsciu '0.04389'


Sterczące kolce Pondijusa, ostre grzebienie Daktyloskopei, Trygla i latający Wieprzoryb są niczym wobec Bestii która nas gnębi...
LP
5 miejsc po przecinku to dobry wynik jest. Sprobuj 4 miejsca po przecinku, albo nawet trzech.
kzkzg
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 7 godzin
  • Postów:926
0

Ale czemu oczekujesz że wyjdzie ci 0.044? Podziel sobie to na kalkulatorze zaokrąglij do 5 miejsc i zobaczysz że dostaniesz to samo


Keep calm and blame frontend.
Tell your cat I said pspsps.
VarrComodoo
nie o to chodzi, doczytaj moj pierwszy post
VarrComodoo
  • Rejestracja:około 14 lat
  • Ostatnio:dzień
  • Lokalizacja:Bk
  • Postów:480
0

Kurczę, czasami trzeba wstać i się przejść.

Do konstruktora przekazuje 'double' sparsowany ze 'string'a', ale nie przepisuje go tylko przeliczam na sekundy i dopiero w metodzie 'WyliczSkladoweZSumySekund' przeliczam jeszcze raz to samo do 'double' ale już z inna dokładnością. Chciałem mieć za wszelka cenę jedna metodę.
Teraz dodałem przeciążoną metodę 'WyliczSkladoweZSumySekund(int s, double d)' i ona tylko przepisuję odczytany 'double' do właściwości, dzięki temu mam to co zostało przeczytane z tekstu i jeżeli nie ruszę tego w czasie życia obiektu 'CzasScala' to tak samo zostanie wyeksportowane do pliku.

Kopiuj
        private void WyliczSkladoweZSumySekund(int s, double d)
        {
            _czasH = (_sumaSekund / 3600);
            _czasM = (_sumaSekund % 3600 / 60);
            _czasS = (_sumaSekund % 3600 % 60);

            _godzinDziesietnie = d;
            _czasStandard = new TimeSpan(_czasH, _czasM, _czasS);
        }

W końcu wszystkie testy przechodzą.


Sterczące kolce Pondijusa, ostre grzebienie Daktyloskopei, Trygla i latający Wieprzoryb są niczym wobec Bestii która nas gnębi...
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)