Forum: Compiler & IDEs große Zahl (lomg long) und trotzdem genau ?


von Mattias (Gast)


Lesenswert?

Hallo liebe Leute,
ich hänge an einem Problem mit sehr großen Zahlen und weiß einfach
nicht, wie ich am besten damit umgehen kann.
Folgendes Problem:
Ich will einem DDS Baustein eine 48bit (6 Byte) tiefe Zahl übergeben,
die von einem Benutzer eingegeben werden soll. Der Benutzer soll aber
nicht die Zahl direkt eingeben, sondern eine Frequenz. Den wert, den
der DDS bekommt, muß erst berechnent werden. Und darin liegt mein
Problem.
Wie kann ich folgendes gut lösen ?

Der Benutzer gibt eine Zahl mit 16 Stellen ein zB.

104.000 000 080 000 0 Mhz ein.
Das ist soweit kein Problem. Ich habe für jeweils einen 3er Block
(104)(000)... eine int Variable i,j,k,l,m,n deklariert und habe dann
die Zahl in einer (long long) Variablen.

tuning_word
=((i*1000000ULL+j*1000ULL+k+l/1000.0+m/1000000.0+n/1000000000.0) * X)

X ist constant und in meinem Fall zB. (2815315.315)
Gibt man nun 1.000 000 000 000 0  ein, dann ist mein
tuning_word =  2815315315000

Mein Problem ist jetzt, dass ich eigentlich  nur ganzzahlige Zahlen
eingebe (i,j,k...) und auch nur eine ganzzahlige Zahl am Ende brauche,
um dort hin zu kommen aber floats habe.

Kann man diese Rechnung nicht irgendwie aufteilen und wieder
zusammenfügen, um so nicht mit floats zu rechnen. Das man nicht jede
beliebige Frequenz einstellen kann, ist schon klar, da das tuning_word
nur ganzzahlig sein kann. Es wird dann halt die nächst mögliche
Freqeunz erzeugt. Es geht mir nur darum, die Rechnung anders
durchzuführen.

Danke schon im voraus.

Mattias

von Karl H. (kbuchegg)


Lesenswert?

Loes die Klammer auf
und berechne die Werte fuer X/1000, X/1000000, X/1000000000
per Taschenrechner als ganze Zahlen und setzte sie ein.

Ich wuerde im uebrigen ueberall mindestens einen Faktor
100 mit dazunehmen, sonst ergibt X/1000000000 naemlich 0
(bei deinem X)

Also alles mal 100 oder 1000
(Ueberchlagsmaessig muesste sich das noch ausgehen. Du benutzt
bis jetzt 6 Bytes, d.h du hast noch 2 Bytes Reserve in einem
long long)

  =( (i * ( X * 1000000000ULL ) +
      j * ( X * 1000000ULL ) +
      k * ( X * 1000ULL ) +
      l * ( X / 1 ) +
      m * ( X / 1000 ) +
      n * ( X / 1000000 ) )   / 1000

Die vielen Multiplikationen bzw. Divisionen mit X sind
kein Problem. Da alles konstant ist, kannst Du mal davon
ausgehen, das das der Compiler zur Compilezeit erledigt.

von Detlef A (Gast)


Lesenswert?

Hallo Mattias, hallo Karl Heinz,

Klammer auflösen ist klar, muß man machen. Zur Skalierung ('Ich wuerde
im uebrigen ueberall mindestens einen Faktor
100 mit dazunehmen, sonst ergibt X/1000000000 naemlich 0'):

der größte Wert ist i*1000000*X, was, wie Mattias richtig sagt, ja
mindestens 48Bit (naja, close) entspricht. Bei ner 64 Bit
Rechengenauigkeit haste dann noch ca. 16 Bit Spielraum, den Du zur
Beibehaltung der Genauigkeit ausnutzen solltest, also maximaler Faktor
könnte ca. 2^16 sein, ist im folgenden snipppet SSH genannt. Naja,
Dynamik muß man nochmal genau ausprobieren, die 6 Additionen kommen
noch dazu, macht auch nochmal 6 Bit, im Beispiel war i allerdings nicht
104 sondern 1, das spart auch 7 BIT pi mal Daumen.

Ach ja, im code nix float, das sollte der Compiler alles vorher
ausrechnen.

Danke für die nette Knobelei.
Cheers
Detlef

#define X (2815315.315)
#define SSH 16
#define FIX (SINT64)((1<<SSH)*X*1e6)
#define FJX (SINT64)((1<<SSH)*X*1e3)
#define FKX (SINT64)((1<<SSH)*X*1e0)
#define FLX (SINT64)((1<<SSH)*X*1e-3)
#define FMX (SINT64)((1<<SSH)*X*1e-6)
#define FNX (SINT64)((1<<SSH)*X*1e-9)
tuning_word=(((i*FIX)>>SSH)+((j*FJX)>>SSH)+((k*FKX)>>SSH)
            +((l*FLX)>>SSH)+((m*FMX)>>SSH)+((n*FNX)>>SSH));

von Peter Dannegger (Gast)


Angehängte Dateien:

Lesenswert?

Ich würde die Rechnungen alle in Assembler machen.

Anbei ein Beispielcode für 56 Bit.

Der ist auch einfach auf beliebige Bitzahlen erweiterbar.


Peter

von Mattias (Gast)


Lesenswert?

Hallo Detlef,
das mit der Klammerauflösung werd ich machen. Was ist denn bitte SINT64
???
Das kennt mein Compiler nicht. Ich denke mal das ist (long long) ?

Mattias

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Was ist denn bitte SINT64?

Ein Alias für den <stdint.h>-Namen int64_t?

von Detlef A (Gast)


Lesenswert?

Hallo,

ja, 'long long' für den AVR. Dachte, das wäre selbsterklärend (wie
'Bratkartofffeln', da ist der Name ja auch Programm): SINT64 = signed
integer, 64 Bit lang. 'unsigned' wäre besser gewesen, aber Hauptsache
64 Bit. Die Lösung von Karl Heinz ist im Prinzip dasgleiche, nur
gefällt dem Prozessor ne integer-Division mit ner Potenz von 2 besser.
Mit 'long long' ist immer solche Sache, das kennt mein Compiler
sowenig wie 'ULL' als suffix.

Cheers
Detlef

von Mattias (Gast)


Lesenswert?

so,
habe mal den Code von Detlef ausprobiert. Mit dem SSH = 16 wird der
Code sehr klein und es funktioniert aber nicht. In t_word steht dann 0
drin. Der Compiler sagt auch:

warning: left shift count >= width of type

Nun, das kann eigentlich nur in der ersten Zeile passieren, wo noch mit
1e6 multipliziert wird. Nun habe ich mal in der 1. Zeile mit SSH = 8 und
sonst mit 16 probiert. Es sind nur die Werte mit SSH = 8 richtig.
Und warum ist der Code wenn SSH = 16 ist so viel kleiner als bei 8 ???

das versteh ich nicht.....
wenn ich das so schreibe, wie Karl Heiz vorgeschlagen hat, dann
funktioniert es schon, der Code wird nur viel größer. Wahrscheinlich
weil der Teiler keine Potenz von 2 ist.

Ich werd mal weiter forschen.

Mattias

von Peter Dannegger (Gast)


Lesenswert?

"104.000 000 080 000 0 Mhz ein."

Und Du glaubst echt im Ernst, daß Du 100nHz Genauigkeit erreichst ???

Und Deine Anwendung ist auch wirklich so empfindlich, daß 100nHz eine
Rolle spielen ???


Peter

von Detlef A (Gast)


Lesenswert?

Hallo,

die '8' von 104.000 000 080 000 0Mhz sind 0.08Hz, das kann ne DDS mit
48Bit und das muß sie auch können, um z.B. ne PLL mit dem Ding
aufzubauen, du willst ja phasenstarr koppeln, da darf nix weglaufen
(obwohl: 104Mhz scheint ja FM Radio zu werden, das geht dann auch nicht
so genau !!??).

Mattias, diese Warning hat vielleicht was mit dem Präprozessor zu tun,
wäre dann vielleicht gut, die defines mit nem Taschenrechner per Hand
zu berechnen und dann direkt einzusetzen !? (die 1 von '1<<SSH' hält
Compiler ja für 'int', das sind beim AVR 16Bit, 16 nach links geht
also nich, mal mit 1ULL probiern?)

Cheers
Detlef

von Peter Sager (Gast)


Lesenswert?

Hallo Leute

Kommt mal wieder runter von Wolke Nr. 7, zurück auf den Boden der
Realität! Ich arbeite mit DDS und numerischen Loopfiltern für extrem
tiefe PLL-Bandbreiten! 32 Bit Auflösung sind für einen 100 MHz DDS in
der Regel mehr als genug, dies ergibt immerhin eine Auflösung von ca.
0.05Hz!

0.05 Hz / 100 MHz => 0.0005ppm

Ich beneide jeden, der einen Referenz-Oszillator genauer als 1ppm sein
Eigen nennt!!!

32 Bit Integer bzw. 10-Digit Dezimalzahlen reichen also völlig aus,
mit dieser Auflösung kannst man "straight forward" programmieren,
ohne irgend welche Zahlenakrobatik! Eine höhere Auflösung ist
Nonsense!

Gruss Peter

von Mattias (Gast)


Lesenswert?

Hallo,
ok, das mit dem 1ULL hat er jetzt gefressen. Allerdings passt der Code
jetzt nicht mehr in mein flash. Ich werde also die defines für jede
Stelle optimal ausrechnen. Eigentlich ist ja nur die 1. und die 2. Zahl
so groß. Den Rest kann ich anders errechnen.

Ich werd berichten, wie ich am wenigsten Platz verbrauche.

Übrigens, ich will weder ein Radio noch ne PLL aufbauen. Die Zahlen
sind willkürlich gewählt. Ich brauche den DDS als präzisen
Frequenzteiler.

Peter,
ich erzeuge meine Referenz mit einem Femtosekunden-Laser und einer
Rep-Rate von mehreren hundert Gigahertz. Damit wird ein Frequenzkamm
erzeugt und die Differenz von Mischprodukten zu einem zweiten Laser
ermittelt. Wer sich dafür interessiert, kann hier nachlesen
http://www.ptb.de/de/org/4/43/sfb/b10/freque.htm
Wenn es einen DDS mit mehr als 48 Bit gäbe, würde ich eher den nehmen.

Mattias

von Mattias (Gast)


Lesenswert?

so,
folgende Schreibweise erzeugt den kleinsten Code:

program(temp_cursor, temp_menu_index,
    ((  i * ( X  1000000ULL      (1ULL<<SSH)) +
      j * ( X  1000ULL      (1ULL<<SSH)) +
      k * ( X  1ULL        (1ULL<<SSH)) +
      l * ( X / 1000ULL *     (1ULL<<SSH)) +
      m * ( X / 1000000ULL *     (1ULL<<SSH)) +
      n * ( X / 1000000000ULL *   (1ULL<<SSH)) )) / (1ULL<<SSH),
    (   o * ( Y ) +
      p * ( Y / 1000ULL)
    )
  );

mit X und Y jeweils fest definiert.
Wenn noch jemand einen Fehler sieht, bitte ich um Nachricht.
Danke für die professionelle Hilfe.

Mattias

von Detlef A (Gast)


Lesenswert?

Hallo Mattias,
hast Du nicht letztens für Deinen Frequenzkamm den Nobelpreis in Physik
gekriegt?
http://www.mpg.de/bilderBerichteDokumente/dokumentation/pressemitteilungen/2005/pressemitteilung20051004/

Ach nee, das war ja der Theodor!

Peter, 5e-10 als absolute Genauigkeit der Frequenz ist schon sehr gut,
ist das nicht die Größenordnung der Genauigkeit von DLF77? Wenn Du mit
einem Empfänger aber kohärent zu einem Sender sein muß, hast Du bei
0.05Hz Frequnzabweichung in 20sec. eine Welle verloren, da kann schon
garnix mehr gehn!

Cheers
Detlef

von Peter Dannegger (Gast)


Lesenswert?

"hast Du nicht letztens für Deinen Frequenzkamm den Nobelpreis in
Physik
gekriegt?"

Na mit dem Code bestimmt nicht.

Wenn der Compiler gut ist, dann optimiert er X * (1ULL<<SSH) /
(1ULL<<SSH) weg zu X.

Und den Ausdruck mit n komplett weg, da ja 2815315.315 / 1000000000ULL
immer 0 ist.


Peter

von Peter Sager (Gast)


Lesenswert?

Okydoky, ich nahem das mit dem "Nonsens" kleinlaut zurück! ;o)

Ich würde die Frequenz in Herz mit Dezimalpunkt eingeben.

z.B. "104000000.0800000"  [Hz]

Den String dann in zwei Teile zerlegen, in einen Hz-Sring und in ein
nHz-String. (Der nHz-String könnte mittels Stringmanipulation auf die
entsprechende Länge erweitert werden, falls man nicht immer alle Nullen
eintippen will.)

Mit der atol() Funktion können die Strings in zwei 32-Bit Integers
konvertiert werden, die dann zu einem 64-Bit Integer zusammengefügt
werden.

Freq_64 = Freq_Hz*1000000000ULL + Freq_nHz;

Da der GCC 64Bit Arithemetik locker beherrscht, müsste dies und die
ganze restliche register Berechnung in 2..3 kByte passen!

Anbei habe ich den Code um meinen 32-Bit DDS zu setzen, diese Funktion
sollte mit kleinen Änderungen auch auf nen 48 Bit DDS anpassbar sein.

typedef signed char        s08;
typedef signed int         s16;
typedef signed long        s32;
typedef signed long long   s64;
typedef unsigned char      u08;
typedef unsigned int       u16;
typedef unsigned long      u32;
typedef unsigned long long u64;


void SPI_set_dds(u32 f_dds)
//---------------------------------------------------
// Set DDS (AD9851) frequency to [f_dds] Hz
// f_dds = 0 will set the DDS into PowerDown Mode
// f_dds = 1..40'000'000 Hz set DDS output frequency
//---------------------------------------------------
#define DDS_SYS_CLK 100000000UL               // 100 MHz (as unsigned
long costant)
#define DDS_MPL 0x0100000000000000ULL         // 2^56 as unsigned long
long const
                                              // multiplicator to
ensure enough bit accuracy
{
  union {u64 reg; u08 byte[8];} freq;         // for easy byte access
to u64
  freq.reg = (f_dds * (DDS_MPL/DDS_SYS_CLK)); // 32 x 32 Bit
multiplication
  PORTB &= ~(1<<CS_DDS);                      // set DDS-Select low
  for (u08 i=3; i<7; i++)
  {
    SPDR=freq.byte[i];                        // send freq.byte[3..6]
over SPI to DDS
    while(!(SPSR & (1<<SPIF)));               // wait until SPI
transfer is complete
  }
  if (f_dds)                                  // if frequency > 0 Hz
then
  {
    SPDR=0x00;                                // DDS-PhaseControlWord
(Phase=0; PowerDown=0; 6xREFCLK=0)
  }
  else                                        // else enable PowerDown
for the DDS
  {
    SPDR=0x04;                                // DDS-PhaseControlWord
(Phase=0; PowerDown=1; 6xREFCLK=0)
  }
  while(!(SPSR & (1<<SPIF)));                 // wait until SPI
transfer is complete
  PORTB |=  (1<<CS_DDS);                      // set DDS-Select high
}


MfG  Peter

von Peter Sager (Gast)


Lesenswert?

Nochmals der selbe Code, will bloss ausprobieren ob es nun mit
Formatierung "schöner" aussieht.
1
typedef signed long long   s64;
2
typedef unsigned char      u08;
3
typedef unsigned int       u16;
4
typedef unsigned long      u32;
5
typedef unsigned long long u64;
6
7
8
void SPI_set_dds(u32 f_dds)
9
//---------------------------------------------------
10
// Set DDS (AD9851) frequency to [f_dds] Hz
11
// f_dds = 0 will set the DDS into PowerDown Mode
12
// f_dds = 1..40'000'000 Hz set DDS output frequency
13
//---------------------------------------------------
14
#define DDS_SYS_CLK 100000000UL               // 100 MHz (as unsigned
15
long costant)
16
#define DDS_MPL 0x0100000000000000ULL         // 2^56 as unsigned long
17
long const
18
                                              // multiplicator to
19
ensure enough bit accuracy
20
{
21
  union {u64 reg; u08 byte[8];} freq;         // for easy u64 byte
22
access
23
  freq.reg = (f_dds * (DDS_MPL/DDS_SYS_CLK)); // 32 x 32 Bit => 64 Bit
24
multiplication
25
  PORTB &= ~(1<<CS_DDS);                      // set DDS-Select low
26
  for (u08 i=3; i<7; i++)
27
  {
28
    SPDR=freq.byte[i];                        // send freq.byte[3..6]
29
over SPI to DDS
30
    while(!(SPSR & (1<<SPIF)));               // wait until SPI
31
transfer is complete
32
  }
33
  if (f_dds)                                  // if frequency > 0 Hz
34
then
35
  {
36
    SPDR=0x00;                                // DDS-PhaseControlWord
37
(Phase=0; PowerDown=0; 6xREFCLK=0)
38
  }
39
  else                                        // else enable PowerDown
40
for the DDS
41
  {  
42
    SPDR=0x04;                                // DDS-PhaseControlWord
43
(Phase=0; PowerDown=1; 6xREFCLK=0)
44
  }
45
  while(!(SPSR & (1<<SPIF)));                 // wait until SPI
46
transfer is complete
47
  PORTB |=  (1<<CS_DDS);                      // set DDS-Select high
48
}

p.s. Wenn der Funktion als Frequenzparameter ein 64-Bitwert in nHz
(statt 32 Bit in Hz) übergibt, brauchts vermutlich den DDS_MPL
Multiplikator nicht mehr.

von Rolf Magnus (Gast)


Lesenswert?

> Wenn der Compiler gut ist, dann optimiert er X * (1ULL<<SSH) /
> (1ULL<<SSH) weg zu X.

Nein, das macht er nur, wenn er fehlerhaft ist, außer wenn SSH den Wert
0 hat.

von Peter Dannegger (Gast)


Lesenswert?

@Rolf,

wo ist denn da der Fehler ?

X == X * Y / Y

Und selbst wenn SSH = 0 ist, dann stimmt es immer noch:

X == X * 1 / 1


Nur anders herum:

X / Y * Y

darf natürlich nicht optimiert werden, da eine solche Funktion sinnvoll
ist, z.B. zum Runden.


Peter

von Mattias (Gast)


Lesenswert?

Hallo Leute,
so, ich habe noch sehr viel experimentiert.
ich habe die Eingabe über mein Display auf die Koeffizienten i,j,k,l
und m beschränkt und habe beschlossen die Werte für nano Herz nur im
remote Betrieb zu setzen (über rs232 direkt in das Register zu
schreiben).
Ich kenne mich mit dem gcc intern wirklich nicht gut aus, folgender
Code erzeugt aber den kleinsten Code und funktioniert bis µhz runter.
i,j,k,l und m können Werte von 0-999 annehmen.


X habe ich fest definiert und den 48bit Wert so berechnet:

#define X (2814749767107)
unsigned (long long) t_word;
unsigned int i,j,k,l,m;


t_word =
(i * X) +
(j * (X / 1000)) +
(unsigned long) (
(k * (X / 1000000)) +
(l * (X / 1000000000)) +
(m * (X / 1000000000000)));

Wenn mir jemand sagen könnte, wie ich diese Rechnung noch kleiner
hinbekomme (zB in asm) währe ich sehr dankbar. Allerdings habe ich von
asm fast überhaupt keine Ahnung. Mein Mega8 ist aber jetzt schon fast
voll.

Mattias

von A.K. (Gast)


Lesenswert?

"X == X * Y / Y"

In der üblichen Mathematik schon. Freilich arbeiten Computer mit einer
speziellen Form der Arithmetik, dank begrenzter Bitzahl. Und da kann in
(X*Y)/Y das Produkt überlaufen, somit hat der Ausdruck nicht die exakt
gleiche Wirkung wie X und der Compiler darf das nicht wegoptimieren.

von Detlef A (Gast)


Lesenswert?

Hallo Mattias,
sieht gut aus, der code. X hat 38Bit, 0..999 hat 10Bit, zusammen 48.
Bißchen Genauigkeit verschenkst Du noch ohne Grund: Du rechnest mit
64Bit, also könntest Du X noch Faktor, naja, 2^14 oder so, aufskalieren
und nachher 14 Bit wieder runterschieben. Codesize: Du mußt 16Bit * 64
Bit multiplizieren, keine Ahnung, was der gcc da aus den libs zubindet,
könnte aber viel sein, mal im map-file kucken. Alternative: Du bastelst
eine Routine, die Dir 16Bit*64Bit mit schieben und addieren rechnet,
dauert dannn aber vermutlich länger.

Cheers
Detlef

von Dieter Pilz (Gast)


Lesenswert?

Hallo!
Da ich in C-programmierung mich nicht auskenne und nur ab und zu in
Assembler programmiere(und auch da nicht perfekt,ich entwickle
eigentlich nur leiterplatten usw.),wer kann mir bitte ein Programm für
den ATmega103/128)für die Programmierung eines AD9852 Tuning Words
(aber hier nur 32Bit,ist ja vollkommen ausreichend)zumailen?
Mir geht es nur um die Berechnung des FTW(fout=2Hoch32/Ftakt).
fout max.100MHz,Schrittweite 10Hz.Und dann die Umrechnung diese
dezimalen Wertes in hex.
Danke
Dieter

von Peter (Gast)


Lesenswert?

Gucke mal etwas weiter "oben", da habe ich meine Funktion für den
AD9851 gepostet! (Inkl. SPI-Bus Ansteuerung) Ich nutze 1 Hz Auflösung
und 32 bit, müsste genauso auchfür den AD9852 tun!

Peter

von Dieter Pilz (Gast)


Lesenswert?

Danke Peter!
Du meinst doch bestimmt die "DIVMUL56.ASM" Datei?
Dieter

von Peter (Gast)


Lesenswert?

Nee, das ist von einem anderen Peter...!  ;o)

Ich meine mein Post mit der Funktion:
void SPI_set_dds(u32 f_dds)

MfG  Peter

von Dieter Pilz (Gast)


Lesenswert?

Danke Peter!
Da ich mich mit C noch nicht beschäftigt habe nützt mir diese Datei
nichts.Analog Device hat ein Tool speziell für den AD9852.Da gebe ich
die Taktfrequenz und eine Ausgangsfrequenz vor,übetrage die Hex-Werte
in den Assembler und brauche dann nur noch ADD oder SUB auszuführen.Die
Multiplikation mit 2^32/Takt kann ich mir sparen.Ich muß nur(!!!) 32Bit
rechnen.32Bit reichen allemal,die LSB-Bit setze ich auf Null.Aber die
32Bit ADD und SUB machen mir schon Probleme.Ich bin halt kein
Softwarespezi,sondern entwickle Schaltungen und Leiterplatten,Analog
und Digital.Ich habe auch die Möglichkeit die Platinen industriell
ätzen zu lassen.Vielleicht ist es zuviel verlangt,aber hast Du
evtl.eine 32+32Bit Assemblerroutine für ATmega128?Soll auch nicht
umsonst sein.
Dieter

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.