Arithmetik doppelter Genauigkeit

Begonnen von Langnese, 02. November 2009, 16:06:47

Vorheriges Thema - Nächstes Thema

0 Mitglieder und 2 Gäste betrachten dieses Thema.

Langnese

[Hallo,

wer hat mit diesen Bausteinen schon einmal gearbeitet. Ich habe das Problem, dass ich bei einer Mittelwertberechnung über folgende Formel arbeiten muss, um die Zyklusbelastung nicht zu groß werden zu lassen:

MW := MW + (Rohwert - buffer[j]); mit j:= 1 bis 3600

Wenn ich jedesmal alle Werte aufaddiere dann ist der Zyklus zu lang.

Leider gibt es bei dieser Berechnung unvermeidbare Rundungsfehler. Also wollte ich das mal mit dem Format REAL2 probieren, erhalte aber immer wieder das selbe Ergebnis, ohne dass die höhere Genauigkeit genutzt wird, der Bereich *.R1 ist immer 0.

Gruß

hugo

kannst du mal ein kleines projekt posten das den fehler aufweist

dalbi

Zitat von: hugo in 02. November 2009, 18:50:49
kannst du mal ein kleines projekt posten das den fehler aufweist

Ja, poste doch bitte mal ein kleines Projekt.
Kannst Du deine Berechnung nicht auf mehre Zyklen aufteilen?

Gruss Daniel

hugo

in deinem Beispiel oben schreibst du z.b. MW + Rohwert ...

mit dem Datentyp REAL2 geht das so nicht, du musst zwingend R2_ADD(MW, ROHWERT) als funktion benutzen.
dasselbe gilt für alle anderen operationen

dalbi

wird bei 3600 Aufrufen aber auch schwierig. :o

Gruss Daniel

Langnese

Hallo,

hier nun endlich die benötigten Infos.

die Programmierung mit Real-Zahlen sieht so aus:

i := (i + 1) MOD Zyklen;
MW := MW + (Rohwert - buffer) / INT_TO_REAL(Zyklen);
buffer := Rohwert;

wobei diese Befehle nur einmal pro Bausteinaufruf bearbeitet werden.

Dann habe ich das auf REAL2 umgesetzt:

i:= (i + 1) MOD Zyklen;
MW_Double:= R2_ADD2(X:= MW_Double, Y:= R2_MUL(X:= (R2_ADD2(X:= Rohwert_Double, Y:= R2_MUL(X:= buffer, Y:= -1))), Y:= Eins_Zyklen));
buffer:= Rohwert_Double;

. Das Ergebnis ist in beiden fällen identisch, auch der *.R1 Anteil bleibt 0.

Die "fehlerfreie" Variante wäre:

FOR k:= 0 TO 3599 DO
    STD_MW_neu:= STD_MW_neu + STD_buffer[k] / 3600;
END_FOR;

und das dauert zu lange.


Inzwischen habe ich mit den Funktionen von REAL2 etwas experimentiert und habe ein kleines Testprojekt aufgesetzt. In der Doku steht:
"Möchte man bis zu Mehreren Mwh (Megawattstunden) an Gesamtverbrauch Aufsummieren und dabei eine kleinste Leistung von 1mW (Milliwatt) im Abstand von 10ms Messen und berücksichtigen, so benötigt man eine Auflösung von 3.6 * 10^7 (entspricht 10MWs) und dabei würde man 1 * 10^-5 Ws aufaddieren wollen.
Um dies zu bewerkstelligen benötigt man eine Auflösung von 12 Stellen."


Also habe ich folgendes programmiert:
      CALL  "R2_SET"
       X      :=1.0 E-5
       RET_VAL:="Test".Eingang1

      CALL  "R2_SET"
       X      :=3.6 E7
       RET_VAL:="Test".Eingang2

      CALL  "R2_ADD2"
       X      :="Test".Ergebnis
       Y      :="Test".Eingang2
       RET_VAL:="Test".Ergebnis

und habe das im OB1 zyklisch mit 1 ms laufen lassen. Das läuft jetzt schon 48 Stunden. Dann hätte eigentlich der Ausgangswert sich um 172.800.000, also sichtbar verändern müssen. Aber der ausgang steht immer noch bei RX=3.6 E7 und R1=0.

Was habe ich dabei falsch gemacht ?

Danke und Gruß

dalbi

#6
Hallo,

was soll "Test".Eingang1 machen der ist in Deiner berechnung ja gar nicht verwendet?

Der Typ REAL2 ist folgender maßen definiert:
*.R1 : REAL   Grobteil der Doppelzahl
*.RX : REAL   Feinteil der Doppelzahl

R2_SET beschreibt lediglich *.RX mit dem Wert von X *.R1 ist immer 0.0, somit hast Du an Deinen Ausgang "Test".Ergebnis.RX immer die 3.6 E7 stehen "Test".Ergebnis.R1 bleibt natürlich 0.
R2_ADD2 addiert X.R1 mit Y.R1 und X.RX mit Y.RX.

Gruss Daniel

dalbi

Halt das ist natürlich Käse
"immer die 3.6 E7 stehen" -> im ersten Zyklus im zweiten sind es dann 3.6E7 + 3.6E7 = 7.2E7 ...

Gruss Daniel

Langnese

Hallo,

sorry, da ist mir ein Tippfehler unterlaufen, aber nur hier im posting.

Programmiert ist:

      CALL  "R2_SET"
       X      :=MD50
       RET_VAL:="Test".Eingang1

      CALL  "R2_SET"
       X      :=MD54
       RET_VAL:="Test".Eingang2

      CALL  "R2_ADD2"
       X      :="Test".Ergebnis
       Y      :="Test".Eingang2
       RET_VAL:="Test".Ergebnis

wobei MD50:= 3.6 E07 und MD54:= 1,0E-05 gesetzt ist. Damit Test.Ergebnis überhaupt initialisert ist, habe ich zum starten den Eingang X von "R2_ADD" einmalig mit Test.Eingang1 beschaltet, so daß Test.Ergebnis mit 3,6E07 initialisiert war.

Ich hoffe, so wird es klarer.

Gruß Peter

dalbi

Hallo,

R2_ADD ist nicht gleich R2_ADD2 nimm mal R2_ADD.

Gruss Daniel

Langnese

Hallo,

das habe ich gemacht:

      CALL  "R2_ADD"
       X      :="Test".Ergebnis
       Y      :=MD54
       RET_VAL:="Test".Ergebnis

und Test.Ergebnis mit der Funktion R2_SET einmalig auf 3.6 E07 gesetzt.

Jetzt ist das Verhalten anders, in Test.Ergebnis.R1 wird pro Zyklus die in MD54 stehenden 1.0E-05 dazuaddiert. Das sieht schon gut aus, aber wenn die Zahl bei ca 1.8 E00 ankommt, wird das Ergebnis plötzlich negativ und es wird von -1,8 wieder auf 1,8 hochgezählt.

Gruß
Peter

e3

Hi,
ich möchte das Thema noch mal aufgreifen.
Den die Funktionen zur doppelten Genauigkeit erfüllen meiner Meinung nach nicht ihren Zweck, bzw. die Erwartungen an Korrektheit.

Aus der Funktion R2_ADD2:
tR2_ADD2.R1 := X.R1 + Y.R1;
tR2_ADD2.RX := X.RX + Y.RX;


Da wird ja nur jeweils der Grob- und Feinanteil für sich alleinstehend aufsummiert. Ein Übertrag von Feinanteil zum Grobanteil findet nicht statt. Soll das so gewollt sein?

Ohne einen Übertrag zu den übergeordneten Stellen ergibt das mathematisch für mich keinen Sinn.

Für Aufklärung wäre ich durchaus dankbar,

mfG

e3



Wisse

#12
I'm sorry for resurrecting an old topic, but I figured it's worth posting since I couldn't find any similar solution on the forum. I hope someone finds this useful while S7-1500 with native double float is gaining traction.


If I understood e3's post correctly, he saw similar problem in R2_ADD2 function as we did. My colleague "mme" came up with this solution based on R2_ADD:
tR2_ADD2.RX = Y.R1 + Y.RX + X.RX + X.R1;
tR2_ADD2.R1 = X.RX + Y.RX - tR2_ADD2.RX + Y.R1 + X.R1


I did a quick and dirty test in C# using floats in place of S7 Real and with double as a reference. The code above appears to be working as expected.
Test code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Real2Test
{
    class Program
    {
        static void Main(string[] args)
        {
            float a = 10000000F;
            float b = 0.00123F;

            double c = a;
            Real2 d = new Real2();
            d.RX = a;
            d.R1 = 0F;

            Real2 e = new Real2();
            e.RX = b;
            e.R1 = 0F;


            for (int i = 0; i < 3333333; i++)
            {
                c -= (double)b;
                d = R2AddNew(d, R2Mul(e, -1F));
            }
            Console.WriteLine("Double: {0:F8}\tR2: {1:F8}\t{2:F8}", c, d.RX, d.R1);

            for (int i = 0; i < 3333333; i++)
            {
                c += (double)b;
                d = R2AddNew(d, R2Mul(e, 1F));
            }
            Console.WriteLine("Double: {0:F8}\tR2: {1:F8}\t{2:F8}", c, d.RX, d.R1);

            for (int i = 0; i < 3333333; i++)
            {
                c += (double)b;
                d = R2AddNew(d, R2Mul(e, 1F));
            }
            Console.WriteLine("Double: {0:F8}\tR2: {1:F8}\t{2:F8}", c, d.RX, d.R1);
            Console.ReadKey();
        }

        static Real2 R2Mul(Real2 x, float y)
        {
            Real2 tmp = new Real2();
            tmp.RX = x.RX * y;
            tmp.R1 = x.R1 * y;

            return tmp;
        }

        static Real2 R2AddNew(Real2 x, Real2 y)
        {
            Real2 z = new Real2();
            z.RX = y.R1 + y.RX + x.RX + x.R1;
            z.R1 = x.RX + y.RX - z.RX + y.R1 + x.R1;
            return z;
        }
       
    }

    class Real2
    {
        public float R1 { get; set; }
        public float RX { get; set; }
    }
}


Thank you for this great library!
W