it-swarm-eu.dev

Co lze udělat s programovacími jazyky, aby se zabránilo nástrahám s pohyblivou řádovou čárkou?

Nepochopení aritmetiky s pohyblivou řádovou čárkou a její nedostatky jsou hlavní příčinou překvapení a zmatku v programování (zvažte počet otázek týkajících se „přetečení zásobníku“, které se týkají „čísel nepřidávajících správně“). Vzhledem k tomu, že mnoho programátorů ještě musí porozumět jeho důsledkům, má potenciál zavést mnoho drobných chyb (zejména do finančního softwaru). Co mohou programovací jazyky udělat, aby se vyhnuly jeho nástrahám pro ty, kteří nejsou s koncepty obeznámeni, a přitom stále nabízejí svou rychlost, když přesnost není kritická pro ty, kteří do chápou koncepty?

28
Adam Paynter

Říkáte "zejména pro finanční software", který vyvolá jeden z mých mazlíčků: peníze nejsou plovák, je to int .

Jistě, vypadá to jako plovák. Má tam desetinnou čárku. Ale to je jen proto, že jste zvyklí na jednotky, které matou problém. Peníze vždy přicházejí v celých číslech. V Americe je to cent. (V určitých kontextech si myslím to mohou být mlýny , ale prozatím to ignorujte.)

Takže když řeknete 1,23 $, je to opravdu 123 centů. Vždy, vždy, vždy mějte matematiku za těchto podmínek a budete v pořádku. Další informace viz:

Přímá odpověď na otázku, programovací jazyky by měly jako přiměřený primitiv obsahovat pouze typ peněz.

aktualizace

Dobře, měl jsem říct „vždy“ dvakrát, nikoli třikrát. Peníze jsou opravdu vždy int; ti, kteří myslí jinak, jsou vítáni, aby mi poslali 0,3 centu a ukázali mi výsledek na tvém bankovním výpisu. Ale jak komentátoři poukazují, existují vzácné výjimky, když musíte udělat plovoucí desetinnou čárku na penězích podobných číslech. Například některé druhy cen nebo výpočtů úroků. I tehdy by se s nimi mělo zacházet jako s výjimkami. Peníze přicházejí a vycházejí jako celá čísla, takže čím blíže váš systém k tomu přistupuje, tím bude zdravější.

47
William Pietri

Poskytování podpory pro desetinný typ v mnoha případech pomáhá. Mnoho jazyků má desetinný typ, ale jsou nedostatečně využívány.

Je důležité porozumět aproximaci, ke které dochází při práci se znázorněním reálných čísel. Používání jak desetinných, tak i pohyblivých řádových řádků 9 * (1/9) != 1 je správný příkaz. Když konstanty, může optimalizátor optimalizovat výpočet tak, aby byl správný.

Poskytnutí přibližného operátora by pomohlo. Taková srovnání jsou však problematická. Všimněte si, že 0,9999 bilionů dolarů se přibližně rovná 1 bilionům dolarů. Mohli byste prosím vložit rozdíl na můj bankovní účet?

15
BillThor

Bylo nám řečeno, co dělat v prvním ročníku (sophomore) přednáška v informatice, když jsem šel na vysokou školu, (tento kurz byl předpokladem i pro většinu přírodovědných předmětů)

Vzpomínám si na lektora, který říkal: „Čísla s pohyblivou řádovou čárkou jsou přibližné hodnoty. Pro peníze použijte celá čísla. Pro přesný výpočet použijte FORTRAN nebo jiný jazyk s čísly BCD.“ (a pak upozornil na aproximaci, pomocí tohoto klasického příkladu 0,2 nelze přesně reprezentovat v binární plovoucí desetinné čárce). To se také ukázalo tento týden v laboratorních cvičeních.

Stejná přednáška: „Pokud musíte získat více přesnosti s pohyblivou řádovou čárkou, seřaďte si podmínky. Přidejte malá čísla dohromady, ne k velkým číslům.“ To mi v mysli zůstalo.

Před několika lety jsem měl nějakou sférickou geometrii, která musela být velmi přesná a stále rychlá. 80 bitů na PC to neřeželo, takže jsem do programu přidal několik typů, které tříděly termíny před provedením komutačních operací. Problém je vyřešen.

Než si stěžujete na kvalitu kytary, naučte se hrát.

Před čtyřmi lety jsem měl spolupracovníka, který pracoval pro JPL. Vyjádřil nedůvěru, že jsme pro některé věci použili FORTRAN. (Potřebovali jsme super přesné numerické simulace vypočítané offline.) „Všechno FORTRAN jsme nahradili C++,“ řekl hrdě. Přestal jsem přemýšlet, proč zmeškali planetu.

9
Tim Williscroft

Upozornění: Typ s pohyblivou řádovou čárkou System.Double postrádá přesnost pro přímé testování rovnosti.

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

Nevěřím, že by něco mohlo nebo mělo být provedeno na jazykové úrovni.

8
ChaosPandion

Ve výchozím nastavení by jazyky měly používat nečíselná čísla s libovolnou přesností.

Ti, kteří potřebují optimalizaci, mohou vždy požádat o plováky. Jejich použití jako výchozího jazyka dávalo smysl v programovacích jazycích C a dalších systémech, ale ne ve většině jazyků, které jsou dnes populární.

7
Waquo

Dva největší problémy týkající se čísel s pohyblivou řádovou čárkou jsou:

  • nekonzistentní jednotky aplikované na výpočty (všimněte si, že to také ovlivní celou aritmetiku stejným způsobem)
  • nepochopení, že FP čísla jsou aproximace a jak inteligentně řešit zaokrouhlování.

První typ poruchy lze napravit pouze poskytnutím složeného typu, který obsahuje informace o hodnotě a jednotce. Například hodnota length nebo area, která obsahuje jednotku (metry nebo čtvereční metry nebo stopy a čtvereční stopy). V opačném případě musíte být opatrní, abyste vždy pracovali s jedním typem měrné jednotky a konvertovali na jiný, když sdílíme odpověď s člověkem.

Druhým typem selhání je koncepční selhání. Selhání se projeví, když je lidé považují za čísla absolutní. Ovlivňuje operace rovnosti, kumulativní chyby zaokrouhlování atd. Například může být správné, že pro jeden systém jsou dvě měření ekvivalentní v rámci určitého rozpětí chyby. Tj. 0,999 a 1,001 jsou zhruba stejné jako 1,0, pokud vám nezáleží na rozdílech, které jsou menší než +/- .1. Ne všechny systémy jsou však tak mírné.

Pokud je potřeba nějaké jazykové zařízení, pak bych to nazval přesnost rovnosti. V testovacích rámcích NUnit, JUnit a podobně vytvořených testovacích rámcích můžete ovládat přesnost, která je považována za správnou. Například:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

Pokud by například C # nebo Java byla změněna tak, aby zahrnovala přesného operátora, mohlo by to vypadat takto:

if(.999 == 1.001 within .1) { /* do something */ }

Pokud však poskytnete funkci, jako je tato, musíte také zvážit případ, kdy je rovnost dobrá, pokud strany +/- nejsou stejné. Například + 1/-10 by považovala dvě ekvivalentní čísla, pokud by jedno z nich bylo o 1 více nebo o 10 méně než první číslo. K řešení tohoto případu bude možná nutné přidat také klíčové slovo range:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }
4
Berin Loritsch

Jedna věc, kterou bych rád viděl, by bylo uznání, že double to float by mělo být považováno za rozšiřující se konverzi, zatímco float to double se zužuje ( *). To se může zdát kontra intuitivní, ale zvažte, co tyto typy ve skutečnosti znamenají:

  • 0,1f znamená "13 421 773,5/134 217 728 plus nebo mínus 1/268 435 456 nebo podobně".
  • 0,1 skutečně znamená 3 602 879 701 896 397/36 028 797 018 963 968 plus nebo mínus 1/72 057 594 037 927 936 nebo podobně "

Pokud má jeden double, který drží nejlepší reprezentaci množství "jedna desetina" a převede jej na float, bude výsledek "13,421,773,5/134,217,728 plus plus mínus 1/268 435 456 nebo tak “, což je správný popis hodnoty.

Naopak, pokud jeden má float, který drží nejlepší reprezentaci kvantity „jedna desetina“ a převede jej na double, výsledek bude „13 421 773,5/134 217 728 plus nebo mínus 1/72 057 594 037 927 936 atd. “- úroveň předpokládané přesnosti, která je chybná faktorem přesahujícím 53 milionů.

Přestože standard IEEE-744 vyžaduje provedení matematických metod s pohyblivou řádovou čárkou jako by každé číslo s plovoucí desetinnou čárkou představuje přesnou číselnou veličinu přesně ve středu jejího rozsahu, což by nemělo znamenat, že hodnoty s plovoucí desetinnou čárkou skutečně představují tyto přesné numerické veličiny. Požadavek, aby hodnoty byly ve středu jejich rozpětí, vychází spíše ze tří skutečností: (1) výpočty musí být prováděny tak, že operandy mají určité konkrétní přesné hodnoty; (2) konzistentní a zdokumentované předpoklady jsou užitečnější než nekonzistentní nebo nezdokumentované předpoklady; (3) pokud bude člověk činit důsledný předpoklad, není žádný jiný důsledný předpoklad lepší než předpoklad, že množství představuje střed jeho rozsahu.

Mimochodem, vzpomínám si asi před 25 lety, někdo přišel s číselným balíčkem pro C, který používal „typy rozsahů“, z nichž každý sestával z dvojice 128bitových plaváků; všechny výpočty by se prováděly takovým způsobem, aby se pro každý výsledek vypočítala minimální a maximální možná hodnota. Pokud jeden provedl velký dlouhý iterativní výpočet a přišel s hodnotou [12.53401391134 12.53902812673], bylo by možné si být jisti, že zatímco mnoho číslic přesnosti bylo ztraceno při zaokrouhlovacích chybách, výsledek mohl být stále přiměřeně vyjádřen jako 12,54 (a nebylo to ' opravdu 12,9 nebo 53,2). Překvapuje mě, že jsem neviděl žádnou podporu pro takové typy v jakýchkoli tradičních jazycích, zejména proto, že by se zdály být vhodné s matematickými jednotkami, které mohou paralelně pracovat s více hodnotami.

(*) V praxi je často užitečné používat hodnoty dvojité přesnosti k udržení mezilehlých výpočtů při práci s čísly s jednou přesností, takže pro všechny takové operace by mohlo být nepříjemné používat typcast. Jazyky by mohly pomoci tím, že budou mít typ „fuzzy double“, který bude provádět výpočty jako dvojitý a bude moci být volně obsazen do az jednoho; to by bylo zvláště užitečné, pokud by funkce, které berou parametry typu double a return double, mohly být označeny tak, aby automaticky generovaly přetížení, které místo toho přijímá a vrací "fuzzy double".

3
supercat

Jedna věc by mohla udělat jazyky - odstranit srovnání rovnosti z typů s plovoucí desetinnou čárkou, než přímé porovnání s hodnotami NAN.

Testování rovnosti by existovalo pouze jako funkční volání, které vezme dvě hodnoty a delta, nebo pro jazyky jako C #, které umožňují typům mít metody EqualsTo, které vezmou druhou hodnotu a delta.

3
Loren Pechtel

Připadá mi divné, že nikdo neuvedl racionální číslo trik rodiny LISP.

Vážně otevřete sbcl a udělejte to: (+ 1 3) A dostanete 4. Pokud do*( 3 2) dostanete 6. Nyní zkuste (/ 5 3) A dostanete 5/3 nebo 5 třetiny.

To by v některých situacích mělo trochu pomoci, že?

3
Haakon Løtveit

Co umí programovací jazyky? Nevím, jestli existuje jedna odpověď na tuto otázku, protože cokoli, co kompilátor/tlumočník udělá jménem programátora, aby usnadnil jeho život, obvykle funguje proti výkonu, srozumitelnosti a čitelnosti. Myslím, že jak C++ cesta (platí pouze za to, co potřebujete), tak Perl cesta (princip nejméně překvapení) jsou obě platné, ale záleží to na aplikaci.

Programátoři stále potřebují pracovat s jazykem a pochopit, jak se s ním zachází s pohyblivými body, protože pokud tomu tak není, budou vytvářet předpoklady a jednoho dne se popsané chování nebude shodovat s jejich předpoklady.

Můj pohled na to, co programátor potřebuje vědět:

  • Jaké typy s pohyblivou řádovou čárkou jsou k dispozici v systému a v jazyce
  • Jaký typ je potřeba
  • Jak vyjádřit úmysly, jaký typ je v kódu zapotřebí
  • Jak správně využít výhody jakékoli automatické propagace typu pro vyvážení jasnosti a účinnosti při zachování správnosti
3
John

Souhlasím, že na jazykové úrovni není co dělat. Programátoři musí pochopit, že počítače jsou diskrétní a omezené a že mnoho z matematických konceptů v nich reprezentovaných je pouze přibližných.

Nevadí plovoucí desetinná místa. Jeden musí pochopit, že polovina bitových vzorců se používá pro záporná čísla a že 2 ^ 64 je ve skutečnosti docela malý, aby se zabránilo typickým problémům s celočíselnou aritmetikou.

3
Apalala

Co mohou programovací jazyky udělat, aby se vyhnuly nástrahám s pohyblivou řádovou čárkou ...?

Používejte rozumné výchozí hodnoty, např. vestavěná podpora pro desítky.

Groovy to dělá docela pěkně, i když s trochou námahy můžete stále psát kód pro zavedení nepřesnosti s pohyblivou řádovou čárkou.

3
Armand

Pokud by více programovacích jazyků vzalo stránku z databází a umožnilo vývojářům určit délku a přesnost jejich číselných datových typů, mohly by výrazně snížit pravděpodobnost chyb souvisejících s pohyblivou řádovou čárkou. Pokud jazyk dovolil vývojáři deklarovat proměnnou jako Float (2), což naznačuje, že potřebovali číslo s plovoucí desetinnou čárkou se dvěma desetinnými místy s přesností, mohlo by matematické operace provádět mnohem bezpečněji. Pokud by to udělalo tak, že by proměnnou představovalo interně celé číslo a vydělilo by 100 před vystavením hodnoty, mohlo by to zlepšit rychlost pomocí rychlejších celočíselných aritmetických cest. Sémantika plováku (2) by také umožnila vývojářům vyhnout se konstantní potřebě zaokrouhlit data před jejich výstupem, protože plovák (2) by inherentně zaokrouhlil data na dvě desetinná místa.

Samozřejmě byste měli vývojáři povolit, aby požádal o maximální přesnost s pohyblivou desetinnou čárkou, když vývojář potřebuje tuto přesnost. A představili byste problémy, kde mírně odlišné výrazy stejné matematické operace mohou vést k potenciálně odlišným výsledkům, protože operace mezilehlého zaokrouhlování, kdy vývojáři nemají ve svých proměnných dostatečnou přesnost. Ale alespoň ve světě databází se to nezdá být příliš velké. Většina lidí nedělá nejrůznější vědecké výpočty, které by vyžadovaly velkou přesnost v průběžných výsledcích.

2
Justin Cave

Jak již uvedly jiné odpovědi, jediným skutečným způsobem, jak se vyhnout nástrahám s pohyblivou řádovou čárkou ve finančním softwaru, je nepoužívat jej tam. To může být ve skutečnosti proveditelné - pokud poskytujete dobře navrženou knihovnu věnovanou finanční matematice .

Funkce navržené k importu odhadů s pohyblivou desetinnou čárkou by měly být jako takové jasně označeny a měly by být opatřeny parametry odpovídajícími dané operaci, např .:

Finance.importEstimate(float value, Finance roundingStep)

Jediným skutečným způsobem, jak se vyhnout úskalím s pohyblivou řádovou čárkou obecně, je vzdělávání - programátoři musí číst a rozumět něco jako Co by měl každý programátor vědět o aritmetice s pohyblivou řádovou čárko .

Nicméně pár věcí, které by vám mohly pomoci:

  • Vyslovím ty, kteří se ptají „proč je přesné testování rovnosti pro plovoucí desetinnou čárku dokonce legální?“
  • Místo toho použijte funkci isNear().
  • Zajistěte a podporujte použití objektů akumulátorů s pohyblivou řádovou čárkou (které přidávají sekvence hodnot s pohyblivou řádovou čárkou stabilněji než jejich jednoduché přidání do pravidelné proměnné s pohyblivou řádovou čárkou).
1
comingstorm
  • jazyky mají desítkovou podporu; to samozřejmě problém nevyřeší, stále však nemáte přesnou a konečnou reprezentaci například ⅓;
  • některé DB a rámce mají podporu typu peněz, to je v podstatě ukládání počtu centů jako celé číslo;
  • existují knihovny pro podporu racionálních čísel; to řeší problém ⅓, ale neřeší problém například √2;

Výše uvedené jsou použitelné v některých případech, ale ve skutečnosti nejsou obecným řešením pro práci s floatovými hodnotami. Skutečným řešením je porozumět problému a naučit se ho řešit. Pokud používáte výpočty s pohyblivou řádovou čárkou, měli byste vždy zkontrolovat, zda jsou vaše algoritmy numericky stabilní. Existuje obrovská oblast matematiky/informatiky, která souvisí s problémem. Jmenuje se numerická analýza.

1
vartec