Forum: Mikrocontroller und Digitale Elektronik ARM-GCC Division größer 64 Bit - wie ohne Genauigkeitsverlust handhaben?


von Walter T. (nicolas)


Lesenswert?

Ich habe mal wieder eine Knobelaufgabe:

Ich habe eine Funktion, die für alle Eingabewerte korrekte Werte liefern 
soll. Es ist eine Division mit großem Zähler und großem Nenner ( der 
Nenner hat eine Größenordnung von 2^33, die Größenordnung des Zählers 
sieht man ja ).

Das folgende einfache Programm funktioniert natürlich nicht für den 
gesamten Wertebereich des Parameters:
1
    
2
#define RATE1 250000
3
#define RATE2 100000   
4
5
/** Skalierung auf 31 Bit
6
 *
7
 * delta_z = (a * L * 2)/(f_0 * f_1)
8
 *
9
 * @param[in] a_spss:  0...UINT32_MAX
10
 * @return delta_z     0...INT32_MAX */
11
uint32_t prm_delta_z(uint32_t a_spss)
12
{
13
    /* Der Zaehler läuft über ab a = UINT32_MAX/4; */
14
    uint64_t dVz = (uint64_t) a_spss * (1ULL<<32) * 2; 
15
    uint64_t dVn = (uint64_t) RATE1 * RATE2;
16
17
    /* Läuft ueber ab a = UINT32_MAX/4 - X */
18
    uint64_t dV = (dVz + (dVn/2))/dVn;
19
20
    /* Ergebnis saettigen auf 2^31-1 */
21
    if( dV >= INT32_MAX )
22
    {
23
        dV = INT32_MAX;
24
    }
25
    return dV;
26
}

Um den Ausgangswert allein von der Formel im erlaubten Wertebereich zu 
erhalten, darf der Eingangswert bis zu ca. 2,5e9, also ca. UINT31_MAX 
betragen. Aber der 64-Bit-Zwischenwert läuft schon deutlich früher über.

Jetzt knobele ich gerade daran, wie ich diese Funktion am geschicktesten 
in ihrem kompletten Wertebereich gültig bekomme.

Anforderungen an die Geschwindigkeit gibt es keine. Meine Version des 
ARM-GCCs kennt keine 128-Bit-Variablen.

Die einfachste Lösung, die mir spontan einfällt, wäre von den 
Nenner-Konstanten den Primfaktor 2 solange abzuspalten, wie es möglich 
ist. Für die genannten Konstanten wären das immerhin 2^7. Also so:
1
uint32_t prm_delta_z(uint32_t a_spss)
2
{
3
    /* Der Zaehler läuft über ab a = UINT32_MAX/4; */
4
    //uint64_t dVz = (uint64_t) a_spss * (1ULL<<32) * 2;
5
    //uint64_t dVn = (uint64_t) RATE1 * RATE2;
6
7
    /* Konstanten Teil von Zaehler und Nenner aufstellen und durch 2 kuerzen */
8
    uint64_t dVz = (uint64_t) (1ULL<<32) * 2;
9
    uint64_t dVn = (uint64_t) RATE1 * RATE2;
10
11
    while( !(dVz&1) && !(dVn&1) )
12
    {
13
        dVz /= 2;
14
        dVn /= 2;
15
    }
16
17
    dVz *= a_spss;
18
19
20
    uint64_t dV = (dVz + (dVn/2))/dVn;
21
22
    /* Ergebnis saettigen auf 2^31-1 */
23
    if( dV >= INT32_MAX )
24
    {
25
        dV = INT32_MAX;
26
    }
27
28
    return dV;
29
}

Aber geht es eleganter und allgemeingültiger?

: Bearbeitet durch User
von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Ohne jetzt deine Anforderungen genau geprüft zu haben. Suchst du was aus 
der Liste?

https://en.m.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software

Matthias

von Alexander S. (alesi)


Lesenswert?

Walter T. schrieb:
> Ich habe eine Funktion, die für alle Eingabewerte korrekte Werte liefern
> soll.
...
> Die einfachste Lösung, die mir spontan einfällt, wäre von den
> Nenner-Konstanten den Primfaktor 2 solange abzuspalten, wie es möglich
> ist.

Wenn mit "alle Eingabewerte" wirklich alle Zahlen von 1 bis max gemeint 
sind, wären da natürlich auch Primzahlen dabei, von denen man keine 
kleinen Primfaktoren wie 2 oder 3 abspalten kann. Oder sind die 
Eingabewerte immer von der Form 2^n*m?

von m.n. (Gast)


Lesenswert?

Hohe Auflösung und hohe Dynamik bieten double-Berechnungen.
Aber das ist Dir sicher wieder zu einfach.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

m.n. schrieb:
> Hohe Auflösung und hohe Dynamik bieten double-Berechnungen.
> Aber das ist Dir sicher wieder zu einfach.
double hat auch nur 64Bit bei ARM benauso wie auch long double.


Walter T. schrieb:
> Meine Version des
> ARM-GCCs kennt keine 128-Bit-Variablen.
Schau dich mal hier um:

mp::int512_t:-)
- 
https://www.boost.org/doc/libs/1_76_0/libs/multiprecision/doc/html/boost_multiprecision/intro.html
- https://github.com/calccrypto/uint128_t

von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

Alexander S. schrieb:
> Oder sind die
> Eingabewerte immer von der Form 2^n*m?

nein, die Eingabewerte sind beliebig. Aber die Konstanten sind fix. 
Notfalls kann ich mit einem _Static_assert prüfen, ob sie Ärger machen 
werden.

m.n. schrieb:
> Hohe Auflösung und hohe Dynamik bieten double-Berechnungen.
> Aber das ist Dir sicher wieder zu einfach.

Genau. Dabei lernt man nichts.

Irgend W. schrieb:
> Schau dich mal hier um:
>
> mp::int512_t:-)
> -
> https://www.boost.org

Boost ist nett - nur irgendwie mit Kanonen auf Spatzen geschossen.

Für mich sieht die obige Fragestellung sehr heftig danach aus, dass es 
einen Trick gibt, den es nützlich wäre zu kennen.

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Anforderungen an die Geschwindigkeit gibt es keine.

Dann mach es einfach so, wie man es auf MCs ohne Divisionsbefehl macht: 
Schieben, Testen, Subtrahieren.
Man kann dann noch abkürzen, indem man testet, ob der Divisor >32, 64 
oder 96 Bit ist.

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.