Just nu i M3-nätverket
Jump to content

Önskat antal memorerade decimaler i en matematisk C++-kalkyl


BjN

Recommended Posts

Bästa C++-entusiaster !

 

Huru algoritmiskt styra beräkningen av ett komplicerat matematiskt uttryck i C++ så, att resultatet initialiseras i datorminnet med önskat antal decimaler, dvs. så, att manipuleringen ej påverkar en redan etablerad cout-ström (ej enbart utskrivning) utan själva källan för denna ström ?.

 

MVH!

BjN

 

Link to comment
Share on other sites

Har några motfrågor:

Vad menar du med

resultatet initialiseras i datorminnet

Vad menar du med

manipuleringen ej påverkar en redan etablerad cout-ström

Vill du i en metod/funktion beräkna ett värde till en given precision?

Vill du trunkera det beräknade värdet till en given precision?

Vad är det för typ på resultatet? (float, double eller något annat)

 

Kan du ge kodexempel på det du försöker uppnå?

 

[inlägget ändrat 2009-02-13 13:05:57 av CKret]

Link to comment
Share on other sites

CKret

Tack för snabb respons.

"Initialisering". Kanske en olämplig benämning. Jag avser att källfilens komplicerade matematiska uttryck ges ett namn som lagras i datorns minne och där ges (initialiseras ?) det slutliga uträknade siffervärdet, som då bör innehålla önskat antal decimaler.

"manipulering". Dålig slentrianmässig benämning. Jag avser att det önskade antalet decimaler ej får vara enbart en avläsning på skärmen på grund av cout-strömmens modulering, utan att det i minnet lagrade siffervärdet med önskat antal decimaler kan användas som sådant för fortsatta analyser.

Jag strävar uttryckligen efter att beräkna källfilens komplicerade matematiska uttryck till ett siffervärde med önskad precision som innebär ett önskat antal decimaler.

Då det gäller decimaltal har jag den föreställningen att "trunkering" kort och gott avser "avhuggning", dvs. slopandet av icke önskade decimaler utan eventull korrigering av den sista kvarblivande decimalen. Jag avser att den sista kvarblivande decimalens värde skall ökas med "1" (++) om den föregående decimalen är = eller > än "5".

Typen är double ev. float.

Exempel: Yta = Orbyta/4 - 0.5 * stax * liax * asin(xfi / stax). För ett visst värde på xfi bör denna skillnad vara 0 (noll). Nu levererar datorn för den första addenden siffervärdet 1.732e16 och för den andra siffervärdet 1.73175e16. Skillnaden är mao.2.5e12 dvs. rätt så stor, då den bör vara 0. Min strävan är nu att datorn skall leverera siffervärdet 1,732e16 även för den andra addenden, ty siffervärden med tre decimalers noggrannhet uppfyller fordringarna mer än väl. Huru åstadkomma detta i C++

med kommandon i källfilen ?.

MVH !

BjN

 

 

Link to comment
Share on other sites

Så du har en funktion som gör beräkningar nu men får fel resultat?

 

Kan du posta beräkningen som den ser ut i källkoden här så ska jag ta en titt på den.

Inkludera alla variabeldefinitioner också.

[inlägget ändrat 2009-02-13 21:57:02 av CKret]

Link to comment
Share on other sites

Tack CKret för engagemanget. Problemets principiella kärna kan åskådlig-göras uttryckligen med den tidigare angivna addenden dvs.

0.5*stax*liax*asin(xfi / stax) för vilken datorn levererar:

1) stax* liax = 2.20493e16 med "stax" och "liax" som "double".

2) asin(xfi / stax) = 1.5708 = pi / 2 där "pi" definierats globalt som

double pi = 3.142 och

double xfi == double stax

Dessa värden ger addenden = 1.73175e16 mot önskat 1.732e16.

Resultatet är mao. ej felaktigt, men däremot oönskat pga. 2 onödiga decimaler. Kan definitionen av "pi" som "double pi" missleda ?, men detta

synes mig å andra sidan vara enda möjligheten att ej reta upp kompila-torn till att ge en varning.

MVH !

BjN

 

Link to comment
Share on other sites

Du skulle kunna göra på följande sätt:

#include <math.h>
double RoundToPrecision(double value, double precision)
{
 double integral;
 double fractional;

 fractional = modf(value, &integral);

 int modifier = pow(10, precision);

 fractional = fractional * modifier;
 fractional = round(fractional);
 fractional = fractional / modifier;

 return integral + fractional;
}

 

Denna kod är otestad men borde ge dig rätt värde.

Den tar in ett värde (value) och precisionen (precision) och avrundar värdet till det angivna antalet decimaler.

Ex:

double roundedValue = RoundToPrecision(1.73175e16, 3);
// roundedValue borde nu vara 1.732e16

 

 

[inlägget ändrat 2009-02-16 09:26:36 av CKret]

Link to comment
Share on other sites

Du skall ha mycken mycken tack CKret för ditt engagemang och din inlevelse i mina bekymmer. Jag har ännu ej prövat avrundningsfunktionen

som du föreslår, men vid en mera ytlig betraktelse synes den mig motsvara just den algoritm som jag söker. Skall nu pröva den på det "regelrätta" sätt som jag anammat, dvs. med en prototyp i källkodens början och en definition utanför "main" i slutet av koden. Den "addend" som jag åberopat i vår kommunikation är bara en av många byggstenar

för det slutliga resultatet, "xfi" är en annan. Det synes mig ej mödan värt att åberopa avrundningsfunktionen för varje enstaka byggsten. Det torde väl räcka att hela "komplexet" avrundas och lagras i minnet till/med önskat värde ?. De enstaka byggstenarna förekommer nämligen som sådana ej i andra sammanhang. Det kan taga en tid för prövningarna, men jag återkommer i vilket fall som helst, hoppeligen med goda nyheter.

 

Link to comment
Share on other sites

Efter närmare titt på ditt problem så kommer inte den ovanstående avrundningen att fungera. (Var för snabb i tanken).

Följande kod fungerar dock (testad och verifierad):

 

#include <math.h>

double CountPower(double x) {
 int power = 0;
 while (floor(x) > 0) {
   power++;
   x /= 10;
 }
 return power - 1;
}

double RoundToPrecision(double value, double precision) {
 double p = CountPower(value);
 double modifier = pow(10, p - precision);
 double roundedValue = value / modifier;
 double integral;
 if (modf(roundedValue, &integral) >= 0.5)
   roundedValue = roundedValue >= 0 ? ceil(roundedValue) : floor(roundedValue);
 else
   roundedValue = roundedValue < 0 ? ceil(roundedValue) : floor(roundedValue);

 roundedValue *= modifier;

 return roundedValue;
}

 

Exempel:

// value får värdet 1.7318e16 (4 decimaler):
double value = RoundToPrecision(1.73175e16, 4);

// value får värdet 1.732e16 (3 decimaler):
double value = RoundToPrecision(1.73175e16, 3);

//value får värdet 1.73e16 (2 decimaler):
double value = RoundToPrecision(1.73175e16, 2);

 

[inlägget ändrat 2009-02-16 12:03:49 av CKret]

Link to comment
Share on other sites

Ok CKret !

Konstaterade samma sak. Efter många om och men med sedvanliga overloaded-, syntax- osv. - fel, återstod identifieraren "round" som kompilatorn avvisade som "undeclared identifier". Till min stora förvåning

lyckades jag uppnå "1 succeeded" med "double round", för att sedan konstatera, att den korrigerade avrundningsfunktionen returnerade value-värdet oavrundat. Den nya och prövade versionen ser mera komplicerad ut. Jag återkommer då fag fått grepp om den. Ännu en gång: Mycken tack.

 

Link to comment
Share on other sites

Gjorde en liten förenkling och ett tillägg:

double RoundToPrecision(double value, double precision) {
 double exponent = floor(log10(value));
 double modifier = pow(10, exponent - precision);
 double roundedValue = value / modifier;
 double integral;
 if (modf(roundedValue, &integral) >= 0.5)
   roundedValue = roundedValue >= 0 ? ceil(roundedValue) : floor(roundedValue);
 else
   roundedValue = roundedValue < 0 ? ceil(roundedValue) : floor(roundedValue);

 roundedValue *= modifier;

 return roundedValue;
}

 

Eftersom du har värden som du vill avrunda så kanske du är mer intresserad av att avrunda till en viss exponent.

Med ovanstående kod så avrundas ett värde med den befintliga exponenten:

RoundToPrecision(1.73212e16, 3) avrundas till 1.732e16

och

RoundToPrecision(1.73212e14, 3) avrundas till 1.732e14

 

Dessa värden är ju inte förenliga och kanske avrundningen inte är önskvärd utan ett önskat förenligt resultat kanske vore 1.7e14.

 

Skrev följande funktion som avrundar till en angiven exponent:

double RoundToExactPrecision(double value, double precision) {
 double modifier = pow(10, precision);

 double roundedValue = value / modifier;
 double integral;
 if (modf(roundedValue, &integral) >= 0.5)
   roundedValue = roundedValue >= 0 ? ceil(roundedValue) : floor(roundedValue);
 else
   roundedValue = roundedValue < 0 ? ceil(roundedValue) : floor(roundedValue);

 roundedValue *= modifier;

 return roundedValue;
}

 

Den är identisk förutom att den inte beräknar exponenten utan den önskade precisionen (exponenten) skall anges:

RoundToExactPrecision(1.73212e16, 13) avrundas till 1.732e16

och

RoundToExactPrecision(1.73212e14, 13) avrundas till 1.7e14 (eller 0.017e16).

 

Dessa två värden är nu avrundade till samma exponent (precision).

 

Det bör dock sägas att den första funktionen inte är felaktig beroende på hur man använder den.

Om man adderar (subtraherar, etc) de två talen och SEDAN anropar funktionen får man samma resultat som men den andra funktionen:

 

double tal1 = 1.73275e16;
double tal2 = 1.73275e14;

// Följande ger korrekta avrundningar:
RoundToPrecision(tal1 + tal2, 3);
RoundToExactPrecision(tal1 + tal2, 13);
RoundToExactPrecision(tal1, 13) + RoundToExactPrecision(tal2, 13);

//Följande ger en ej önskvärd avrundning:
RoundToPrecision(tal1, 3) + RoundToPrecision(tal2, 3);

 

 

[inlägget ändrat 2009-02-18 08:06:33 av CKret]

Link to comment
Share on other sites

Ok CKret !

Mycken tack för tillägget. Jag har ännu ej hunnit studera det, ty jag har kört fast i föregående inlägg. Det som medför svårigheter är:

1) Funktionen "CountPower". Har någonting i "while-slingans" villkorssats

fallit bort vid utskrivningen, ty jag uppfattar slingan som "evig" pga. att

"floor value" alltid synes mig > 0 oberoende av variabeln "power". Ej heller förmår jag lista ut det matematiska sambandet mellan villkoret och "power". Jag kan ana mig till att "power" bör vara en "int -exponent" pga. variabeln "p" i funktionen "RoundToPrecision" som anropar den förstnämnda funktionen. Både denna första funktion, som synes mig returnera en "int", och även variabeln "p" är dock båda typbestämda som

"double". Här faller jag ur kärran. I alla mina prövningar påtalar kompila-

torn, som i många andra tidigare sammanhang, uttryckligen den irriteran-

de double / int - förbistringen. Om jag lyckas undvika denna, meddelas ett

svårspårbart linkningsfel.

2) Funktionen "modf". Denna är antagligen en biblioteksfunktion. Den accepteras nog av kompilatorn, men det är bara jag som ej får grepp om den pga. att jag ej har tillgång till dess definition. Kan för övrigt det fak-tum att jag inkluderar "cmath" + "namespace.std" isf. "math.h" ha någon inverkan på mina svårigheter ?. Detta har nämligen någon gång förekom-mit.

C++ synes mig vara ett språk som ej så sällan gäckar det som man tyker sig vara logiskt. Korrigera mig i det som är felaktigt i mina utgju-telser.

MVH !

BjN

 

 

 

 

 

Link to comment
Share on other sites

Svar:

1)

CountPower beräknar exponenten i talet.

while slingan bör terminera eftersom x minskar för varje loop och till slut kommer vara 0.

floor(x) avrundar till närmaste helttal MINDRE än x.

Ex:

floor(0.123) => 0.0

floor(14.923) => 14.0

 

CountPower(value) har i förenklingen bytts ut till:

floor(log10(value));

som gör samma sak.

 

int power i CountPower kan likväl vara double power men du kan bortse från hela CountPower() funktionen pga förenklingen.

 

2)

modf är mycket riktigt en biblioteksfunktion och skall finnas i cmath likväl som i math.h

 

modf() delar upp ett tal i två delar, heltalsdel och decimaldel:

int intpart;
double fractionpart = modf(14.1234, &intpart);

// intpart innehåller nu 14.0.
// fractionpart innehåller nu 0.1234.

 

Men eftersom dina värden inte innehåller decimaler per se utan är ett decimaltal med en exponent med basen 10:

1.1234e16 = 11234000000000000.0

så är denna beräkning betydelselös.

 

De nya funktionerna, RoundToPrecision och RoundToExactPrecision, avrundar sådana tal till den givna precisionen. (Se föregående inlägg.)

RoundToPrecision(1.1234e16, 2) = 1.12e16 = 11200000000000000.0

RoundToExactPrecision(1.1234e16, 14) = 1.12e16 = 11200000000000000.0

 

 

Link to comment
Share on other sites

Ok CKret !

Måste erkänna att jag ej fått grepp om avrundningsgången. Tankegången är mycket elegant, men kompilatorn protesterar. Det beror nog på att jag ej till fullo behärskar math-bibliotekets definitioner. Ett stort bekymmer har långa tider varit "pow"-operatorn. Den fungerar ej om exponenten anges som en bokstavssträng, trots att exponenten deklarerats och initia-liselerats på regelrätt sätt. Om exponenten ges ett positivt heltalsvärde, då fungerar det. Men tex "pow(10,-1)=0", ej 0.1 som sig borde. Med varia-beln "precision"=3 resp 13 i de två avrundningsfunktionerna försvann kompileringsfelet, men det OAVRUNDADE värdet returnerades. Då fastna-de jag i "modf"-funktionen. Här får jag ej ordning på variabelns "integral"

andel i systematiken. Den synes mig ej vara definierad i förhållande till funktionens biblioteksstandardbegrepp (&fraktionpart ?). Variabeln "modi-fier" borde enligt min (miss)uppfattning vara ett positivt heltal enligt algo-ritmen, men samtidigt även "modifier=value / roundedValue", vilket i detta exempelfall ej kan vara ett heltal. "&" framför "integral" förbryllar.

"Address" eller "AND" ?. Kompilatorn reagerar med ett "undeclared iden-tifier" i alla mina försök. Som sagt, jag måste göra en grundlig genomgång

av C++math-algebran. Kan du rekommendera något klartextverk för detta ?

MVH !

BjN

 

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.



×
×
  • Create New...