Sida 1 av 2

C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 09:39:35
av jesse
Jag anger gärna parametrar i C-program som flyttal, eftersom det är snyggast och det kan räknas på utan att man tappar i noggrannhet. T.ex

Kod: Markera allt

#define HV_LADDSTOP 185.6 // volt
och använder sedan detta i olika jämförelser med heltalsvariabler, t.ex:

Kod: Markera allt

uint16_t high_volt; // battierspänning i volt*10 (t.ex. 1234 = 123.4 volt)
if (high_volt > HV_LADDSTOP_VOLT*10) { ... }
Problemet är att kompilatorn (GCC) då omvandlar heltalet till float innan jämförelsen, så för att undvika detta måste jag lägga till en typomvandling i if-satsen:

Kod: Markera allt

if (high_volt > (uint16_t)(HV_LADDSTOP_VOLT*10)) 
och det är här nånstans jag tycker det börjar bli lite 'ful'-kod. Risken är väldigt stor att jag glömmer att göra typomvandlingen på flera ställen och att kompilatorn därmed börjar utföra operationer i float istället för i int. Jag skulle vilja ha ett smartare sätt at skriva på , som dels ger mig möjligheten att ange konstanter i float, men att de ändå tolkas som int när man jobbar med dem. Tydligast i koden är väl om jag gör typomvandlingen i if-satsen (som ovan), så att man verkligen ser vilka typer man jobbar med, men finns det då något sätt att ta reda på om det utförs operationer eller jämförelser i float utan att behöva lusläsa hela koden? Nån slags kodanalysator som lämnar lite statistik på vilka operatorer som används och på vilka typer?

(Ett annat typiskt exempel där jag vill använda float men inte räkna i float är om jag vill multiplicera ett heltaltal med en liten konstant:

Kod: Markera allt

#define KONSTANT 0.777 // talet ska multipliceras med denna konstant
uint16_t input_data;
uint16_t high_volt;
high_volt = (input_data * (uint32_t)(KONSTANT *65536)) / 65536; // multiplicera med konstant
Här måste jag ändå se till att använda uint32_t istället för uint16_t om jag inte ska få overflow, så där ska ju typomvandlingen vara tydlig och synlig för att undanröja alla tvivel.)[/color][/i]

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 10:02:23
av SvenW
som dels ger mig möjligheten att ange konstanter i float, men att de ändå tolkas som int när man jobbar med dem.
Tänk en gång till här!
Använd gärna const i stället för define. Det ger kompilatorn större möjligheter till typkontroll.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 10:35:48
av Icecap
Jag kan till viss del förstå önskan om att ange konstanter som float och sedan räkna som heltal men precis som det skrivs kan det ge en massa intressanta felkällor om man inte gör helt rätt varje gång.

Alltså skulle jag välja att göra något liknande:
#define US_HV_LADDSTOP 185.6 // Voltage

Senare i programmet kommer då:
#define HV_LADDSTOP (uint16_t)(X_HV_LADDSTOP * 10.0)

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 10:51:06
av blueint
Alternativt:
#define HV_LADDSTOP (((uint16_t)X_HV_LADDSTOP) * 10)

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 10:56:54
av Icecap
Nja... risken finns att kompilern tar (uint16_t)X_HV_LADDSTOP först vilket betyder att decimalerna sopas bort och SEDAN multipliceras det.

Vid att tvinga kompilern att multiplicera de två flyttal med varandra och SEDAN casta till u_int16_t bör detta inte ske.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 11:11:44
av TomasL
Varför använder du inte heltal, går betydligt snabbare än flyttal och tar mindre minnesutrymme.
Och du tappar ingen precisionen heller.

Dessutom skulle jag nog definiera dem som const istället för #define.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 12:26:14
av victor_passe
Det är väl precis det som TS vill, använda heltal och inte flyttal.

Du skulle kanske kunna göra ett macro:
#define TO_FIXPOINT(x)((int)(x))

Sedan använda det på dina defines.
Det blir ju samma sak men det ser kanske lite bättre ut.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 12:43:58
av TomasL
Jag anger gärna parametrar i C-program som flyttal
Jag uppfattar det som han vill använda flyttal.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 12:48:56
av Icecap
TomasL: Önskan verkar vara att ha en pseudo-flyttal, antagligen för att kunde lätta förståelsen vid läsning av programmet.

Att själva värdet sedan behandlas som heltal i 10x upplösning är en annan sak. Själv anger jag värdet i rätt upplösning med en kommentar om att det ska vara i t.ex. 0,1V, då blir det ganska tydligt när man skriver in värdet.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 13:18:03
av Gimbal
Ogillar att ange konstanter med högre precision än vad som sedan verkligen används. Det om något är väl förvirrande?

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 14:27:03
av adent
Ska man ha in Volt i någon konstant kan man t.ex. ange det i mV istället och på så vis komma runt flyttalsproblematiken, men jag förstår önskan om att skriva konstanter på "rätt" bas.

Men INT-macrot är nog rätt väg att gå isåfall.

MVH: Mikael

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 16:46:17
av jesse
Jag vill alltså skriva i flyttals-form, men att kompilatorn använder talet som ett heltal, bara för att man ska kunna ange parametrar i "klartext", precis som Icecap gör i sitt exempel.
Gimbal skrev:Ogillar att ange konstanter med högre precision än vad som sedan verkligen används. Det om något är väl förvirrande?
Jo, men det gör jag ju inte.

Nu filosoferar jag lite mer om detta...

Det bästa vore ju om man kunde ange någon form av fixed-point form som egen typ, och att en preprocessor omvandlar dessa till heltal, skulle vara jättesmidigt... hur man nu skulle döpa sådana typer?

Kod: Markera allt

/* heltalstyp: fix_d2: */
/* 16-bitars fixtal som anges i hundradelar */
/* typen har omfånget 0 - 655.36 */
/* konstanter anges som d2(1.00) */
#define d2(x) ((uint16_t)(x * 100);
typedef uint16_t fix_d2; 
 
fix_d2 current = d2(3.75); // Ampere
if (current > d2(3.5)) ...
istället för att skriva bara

Kod: Markera allt

 uint16_t current = 375; // Ampere
if (current > 350) ... 
Det senare ser ju betydligt mer läsbart ut.... Frågan är om det allra vettigaste ändå är att skriva allt som heltal ändå:

Kod: Markera allt

#define MAX_CURRENT 2500 // Ampere * 100: upper limit for allowed curent.
eller

Kod: Markera allt

#define MAX_CURRENT 25*100 // Ampere * 100: upper limit for allowed curent.
Allra helst skulle man vilja kunna skriva in formler med delresultat som i sin helhet beräknas och omvandlas till rätt typ innan det når kompilatorn. jag vill gärna göra så här t.ex.

Kod: Markera allt

/********************************** mätning High Volt ******************
		Spänningsdelare R1 = 330k * 4, R2 = 18k ger kvoten 1/74.333 = .013453
		 Spänningen jämförs med 2.50 volt referens.
		 t.ex. 129,6 volt --> 1.74V vilket är 69,74% av 2.50V ref.
		 100% motsvaras av 4096, så 69,74%*4196 = 2856 ADC RÅDATA.

		 Omvänt så tar man rådatavärdet och dividerar med 22.04 så får man spänningen i volt:
		 2856 / 22.04 = 129,6 volt. Spänningen sparas i formen V*10
		 **********************************************************************/
		#define R1 1320.0 // kiloohm
		#define R2 18.0 // kiloohm
		#define  SP_DEL		(R2/(R1 + R2)) // spänningsdelning 330k+18k --> 0.01345
		#define LSB_VAL		((2.50 / 4096) / SP_DEL) // ref 2.50V = 4096.
		#define  SP_DELARE	(u16)(LSB_VAL * 65536 * 10) // divsor 65536, spänning anges i volt*10
Här har jag krånglat till det något, men att skriva det kortare skulle bli ännu svårare att förstå.
Först skriver jag in värden på två motstånd och räknar ut vad kvoten blir mellan U och U1 i en spänningsdelare (SL_DEL).

Sedan tar jag referensspänningen till ADC:n och dividerar med upplösningen och med kvoten.
Svaret blir vad 1 LSB i ADC'n motsvarar i volt innan spänningsdelaren (LSB_VAL).
Av detta skapar jag en konstant som jag ska multiplicera mitt ACD-värde med för att direkt få ut spänning i volt * 10, om jag bara delar resultatet med 65536 (SP_DELARE).

värdet används sedan när jag vill veta hur många volt jag har:

Kod: Markera allt

u16 readHighVolt(void) // volt*10 - driftbatteri, max 185.6 volt mätbart.
{
	return ((u32)mcp3301_read(ADC_HVOLT) * HV_SP_DELARE) >> (16);
}
Det jag inte gillar med den metoden är att man skapar en massa globala #defines som kanske krockar med andra #defines med samma eller liknande namn. Och ju fler defines man har desto svårare att hålla reda på till vilken programsnutt de hör till. I fallet med spänningsdelare kan man ju använda const istället och lägga det lokalt i funktionen. Då blir det ordning och reda. Det enda som skulle vara lämpligt att lägga utanför själva funktionen är ju värdena på R1 och R2 - dessa kanske man vill definiera som parametrar i en header-fil ihop med övriga hårdvarurelaterade parametrar. Vad man inte vill är ju att sprida ut sådana data överallt i programmet så att det blir ett hel* att hitta det sedan om man ska ändra på det.

Det lutar åt att jag borde göra något som liknar detta:

Kod: Markera allt

/* otestad kod */
/* Spänningsdelare för high-volt ADC */
/* Hårdvaruparametrar */

const u16  r1 = 1320;  // kiloohm
const u16  r2 =    18;  // kiloohm
const float referens_voltage = 2.50; // ADC referensspänning, i volt 

...

u16 readHighVolt(void) // volt*10 - driftbatteri, max 185.6 volt mätbart.
{
	/* Använder sig av konstanterna r1 och r2 för att beräkna spänningen */
	const float kvot = (r2*1.0/(r1+ r2)) // beräkna spänningsdelning
	const float volt_per_LSB = ((referens_voltage / 4096) / kvot) // ADC upplösning = 4096
	const u32 heltal_multiplikator = (volt_per_LSB * 65536 * 10) // divsor 65536, spänning lagras som volt*10

	return ((u32)mcp3301_read(ADC_HVOLT) * heltal_multiplikator) >> (16);
}
:oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops: :oops:

Detta blir mer och mer förvirrande... Det viktigaste är kanske att man är konsekvent och gör på samma sätt i hela koden, så att man inte blandar de olika metoderna vilt... Mitt problem är väl att jag inte kan bestämma mig för vad som är vettigast att göra. :humm:

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 19:16:09
av TomasL
Om du använder tal med kommatecken, oavsett om det är i konstanter eller i defines, kommer alltid kompilatorn initialt att tolka det som en float, oavsett om du castar det direkt.
Bättre att använda heltal rätt igenom och const.

Re: C programmering: konstanter i float --> int

Postat: 14 oktober 2013, 20:36:10
av SvenW
Ja, jag tror också det är bäst att använda heltal i maskinnära enheter
som ADC_steg och räkna exakt så långt som det är möjligt.
I sista steget konverterat man om det är nödvändigt.

Man får lätt trunkeringsfel som kan se ut att vara försumbara men likväl
förödande i t.ex. sevosystem.

Det gäller att överallt i koden vara tydlig med vilka enheter man använder.
Man kan använda kommentarer, långa variabelnamn eller typdeffar med bra namn.
Typdeffar är bra om typen används på många ställen, och i anslunting till
typdeffen kan man kommentera närmare hur man tänker, så man inte behöver lägga
samma information i kommentarer på många ställen.

ex:
typedef int16_t AdcSteps; // One ADCstep is 1 mV
...
const AdcSteps v_ref = 4096;
...

Re: C programmering: konstanter i float --> int

Postat: 15 oktober 2013, 00:10:52
av Findecanor
jesse skrev:Jag anger gärna parametrar i C-program som flyttal, eftersom det är snyggast och det kan räknas på utan att man tappar i noggrannhet.
Flyttal skrivs i decimal, men lagras i binärt, dvs. basen 2. Visst får du precision, men det är inte alltid som det du skrivit är exakt det som lagras i variabeln. T.ex. om du skriver "0.1" så får du egentligen "0.10001".
Jag tror inte heller att det är vanligt att microcontrollers har något flyttalsenhet, vilket gör att de måste emuleras, vilket inte har någon bra prestanda och därför bör undvikas i tidskritisk kod.
Använd fixed point! Då får du prestanda, precision och kompilatorn kan uttrycken kan optimeras ner till konstanter.
Jag föredrar att använda fixed point med en faktor som är 2**(heltal). De kan oftast optimeras väl av kompilatorn.