Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - mod-Operator


knittel - Mi 21.08.13 01:44
Titel: mod-Operator
Hallo allerseits,

Ich hab um einen beliebigen Winkel in der Rahmen von 0 und 360 zu bringen den module Operator verwendet. Einfach result := Winkel mod 360.
Doch sobald es zu negativen Zahlen kommt, ist der Winkel stattdessen


Tranx - Mi 21.08.13 04:21

Bei x mod y bekommst Du nur ganzzahlige Werte, da Du nur ganzzahlige Werte einsetzen darfst. Aber unabhängig davon, mit

Delphi-Quelltext
1:
   y := abs(x) mod 360;                    


solltest Du das gewünschte Ergebnis bekommen.

Und für nicht integer:


Delphi-Quelltext
1:
   y := abs(x - int(x/360)*360);                    


Boldar - Mi 21.08.13 06:03

oder aber

Delphi-Quelltext
1:
2:
while (x<0) x:=x+360;
x:=x mod 360;

Für bessere Performance evtl. die 360 durch größere Zahlen ersetzen, z.B. ((maxint div 360)*360)


Gammatester - Mi 21.08.13 09:13

user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
y := abs(x) mod 360;
//..
y := abs(x - int(x/360)*360);
Die beiden Formeln sind alllerdings offensichtlich falsch, da zB -30 nach 30 transformiert wird! Das bedeutet zB für den einfachen Sinus, das Du falsche Vorzeichen hast. Die einfachste und schnellste Möglichkeit nur mit Delphibordmitteln ist meiner Meinung nach

Delphi-Quelltext
1:
2:
y := x - int(x/360.0)*360.0;     // Kann zwischen -360 .. +360 liegen
if y < 0.0 then y := y + 360.0;  // Korrektur falls negative


jasocul - Mi 21.08.13 10:10

Habe nur ich das Gefühl, dass hier mit Kanonen auf Spatzen geschossen wir?

Delphi-Quelltext
1:
2:
3:
NeuerWinkel := AlterWinkel mod 360;
if NeuerWinkel < 0 then
  NeuerWinkel := NeuerWinkel + 360;


Xion - Mi 21.08.13 11:17

Fast, ich würde sagen:


Delphi-Quelltext
1:
2:
3:
NeuerWinkel := AlterWinkel mod 360;
while NeuerWinkel < 0 do
  NeuerWinkel := NeuerWinkel + 360;


Aber das hatte user profile iconBoldar schon so in etwa :mrgreen:

oder ganz ohne mod:

Delphi-Quelltext
1:
2:
3:
4:
while NeuerWinkel >= 360 do
  NeuerWinkel := NeuerWinkel - 360;
while NeuerWinkel < 0 do
  NeuerWinkel := NeuerWinkel + 360;


Das Ding heißt übrigens nicht module sondern modulo


jasocul - Mi 21.08.13 11:21

@Xion:
Das while ist unnötig, da das vorherige Modulo schon dafür sorgt, dass der neue Winkel im richtigen Bereich ist. :wink:


Gammatester - Mi 21.08.13 11:24

user profile iconjasocul hat folgendes geschrieben Zum zitierten Posting springen:
Habe nur ich das Gefühl, dass hier mit Kanonen auf Spatzen geschossen wir?

Delphi-Quelltext
1:
2:
3:
NeuerWinkel := AlterWinkel mod 360;
if NeuerWinkel < 0 then
  NeuerWinkel := NeuerWinkel + 360;
Richtig! Die Kanonen müssen ja auch nur dann zu Einsatz kommen, wenn die Winkel keine Integer sind, was also wahrscheinlich meist der Fall ist, außer vielleicht für einzelne Spezialfragen oder eine Tabelle der von reduzierten Integerwinkeln :wink:


Xion - Mi 21.08.13 11:29

user profile iconjasocul hat folgendes geschrieben Zum zitierten Posting springen:
@Xion:
Das while ist unnötig, da das vorherige Modulo schon dafür sorgt, dass der neue Winkel im richtigen Bereich ist.

Hmm, also das ist mir zu vage. Warum sollte ich nicht zweimal einen Winkel abziehen, bevor ich ihn wieder normalisiere? Das Modulo hat ja keinen Effekt bei negativen Zahlen (zumindest bei meinem Delphi bin ich mir da zu 99,9% sicher). So komm ich leicht auf -400 Grad. Mir ist da eine sichere, vielleicht minimal langsamere Variante doch lieber ;)


jasocul - Mi 21.08.13 11:58

@Gammatester:
Modulo ist für ganzzahligen Werte. user profile iconknittel nutzt das ja schon. Sollten allerdings auch nicht-ganzzahlige Typen verwendet werden, hast du natürlich Recht.

@Xion:
Bei XE2 funktioniert Modulo auch bei negativen Werten.


Tranx - Mi 21.08.13 12:47

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
y := abs(x) mod 360;
//..
y := abs(x - int(x/360)*360);
Die beiden Formeln sind alllerdings offensichtlich falsch, da zB -30 nach 30 transformiert wird! Das bedeutet zB für den einfachen Sinus, das Du falsche Vorzeichen hast. Die einfachste und schnellste Möglichkeit nur mit Delphibordmitteln ist meiner Meinung nach

Delphi-Quelltext
1:
2:
y := x - int(x/360.0)*360.0;     // Kann zwischen -360 .. +360 liegen
if y < 0.0 then y := y + 360.0;  // Korrektur falls negative


Alternativ: Um die FALSCHE Formel zu verbessern:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
// Integerzahl
y := x mod 360;
if y <0 then 
  y := y + 360;

// Realzahl
y := abs(x - int(x/360)*360);
if y <0.0 then 
  y := y + 360;


knittel - Mi 21.08.13 13:09

ui^^ ne ganze Mengen Antworten. Danke an alle schonmal! :)
Es war zwar garnicht so sehr das Problem wie ich das löse, dafür verwende ich:


Delphi-Quelltext
1:
2:
3:
4:
5:
function ClampToAngle(const A: single): single;
begin
result := Trunc(A) mod 360 + Frac(A);
if result < 0 then result := result + 360;
end;


(auf while wollte ich aus Performance-Gründen verzichten)

Ich hab mich nur gefragt warum mod so arbeitet. Ich dachte immer mod würde (an einem Zahlenstrahl erklärt) so arbeiten:

x mod 4

Quelltext
1:
2:
x: -9|-8|-7|-6|-5|-4|-3|-2|-1|0|1|2|3|4|5|
y:  3| 0| 1| 2| 3| 0| 1| 2| 3|0|1|2|3|0|1|


Gammatester - Mi 21.08.13 13:39

user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:
Alternativ: Um die FALSCHE Formel zu verbessern:

Delphi-Quelltext
1:
2:
3:
4:
// Realzahl
y := abs(x - int(x/360)*360);
if y <0.0 then 
  y := y + 360;
Das ist nun definitiv Unsinn. y=abs() kann nicht negativ sein, also bringt die if-Anweisung überhaupt nichts, und -30 wird immer noch in +30 umgewandelt und nicht in 330.


Mathematiker - Mi 21.08.13 14:20

Hallo,
so ein ähnliches Problem kenne ich bei der Berechnung des Wochentages mit der Zellerschen Formel.
Meine Lösung war (auf Dein Problem angewendet):

Delphi-Quelltext
1:
2:
3:
4:
 result := (winkel + 360mod 360;
oder
 result := (winkel + 3600mod 360;
oder noch größere Summanden

Da habe ich kein if, while usw. drin, sondern "nur" eine Addition.
Wahrscheinlich ist das wieder nicht so elegant, aber es funktioniert.

Beste Grüße
Mathematiker


Gammatester - Mi 21.08.13 14:42

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Da habe ich kein if, while usw. drin, sondern "nur" eine Addition.
Wahrscheinlich ist das wieder nicht so elegant, aber es funktioniert.
Wie wir inzwischen erfahren haben, will user profile iconknittel ja doch eigentlich Singlewinkel normalisieren und wählt einen Weg über trunc() mod 360 + frac(). Selbst wenn man Deinen Ansatz einsetzen würde, hätte man

Delphi-Quelltext
1:
result := (Trunc(A) + 3600mod 360 + Frac(A)                    
Trotzdem käme man wegen des möglichen negativen Frac(A) praktisch nicht um ein if herum, weil das Ergebnis noch negativ sein kann (oder man müßte noch mehr Aufwand treiben um ein if zu vermeiden).


Tranx - Mi 21.08.13 15:03

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconTranx hat folgendes geschrieben Zum zitierten Posting springen:
Alternativ: Um die FALSCHE Formel zu verbessern:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
// Realzahl
y := abs(x - int(x/360)*360);
if y <0.0 then 
  y := y + 360;


korrekt:
if x<0.0 then
  y := 360.0 + x - 360*int(x/360)
else
  y := x - 360*int(x/360);
Das ist nun definitiv Unsinn. y=abs() kann nicht negativ sein, also bringt die if-Anweisung überhaupt nichts, und -30 wird immer noch in +30 umgewandelt und nicht in 330.


Entschuldigung, dass ich das abs nicht gelöscht hatte

bei 30 ergibt das 30 - 360*0 = 30 bei -30 : 360 - 30 - 360*0 = 330
bei 750 ergibt das 750 - 360*2 = 30 bei -750: 360 - 750 - 360*-2 = 330

Das sollte nun korrekt sein.


IhopeonlyReader - Mi 21.08.13 15:17


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
  NeuerWinkel := (Abs(Trunc(AltWinkel))-1mod 360 +1;
if AltWinkel<0 then
  NeuerWinkel := (360 - NeuerWinkel) + Frac(AltWinkel)
else
  NeuerWinkel := NeuerWinkel + Frac(AltWinkel);
if NeuerWinkel > 360 then
  NeuerWinkel := NeuerWinkel - 360
else
  if NeuerWinkel < 0 then
    NeuerWinkel := NeuerWinkel + 360;

Ach ich liebe die Vielfalt der Mathematik :D


Gammatester - Mi 21.08.13 15:18

Langsam glaube ich auch an die Kanonen/Spatzen-Theorie von user profile iconjasocul. Alles wäre einfacher, wenn Delphi ein mathematisch korrektes mod hätte, das auch für Fließkommazahlen definiert wäre, ähnlich fmod in C. Man kann es natürlich mit floor aus der Math-Unit nachbilden und hätte dann (ohne if):

Delphi-Quelltext
1:
2:
3:
4:
function ClampToAngle(const A: single): single;
begin
  result := A - floor(A/360.0)*360.0;
end;


IhopeonlyReader - Mi 21.08.13 15:21

ok, gammatester hat wohl damit den thread beendet :D
das ist die einfachste und warscheinlich schnellste methode


Gammatester - Mi 21.08.13 15:26

user profile iconIhopeonlyReader hat folgendes geschrieben Zum zitierten Posting springen:
Gammetester.. es bleibt dabei -0,12° <> 0,12° sondern = 359,88° !
Huh :autsch: Meine Funktion liefert die 359,88°, vielleicht hast Du nicht mitbekommen, daß 0,12° nicht das gewünschte Ergebnis ist. - Oder ich bin im Tran und mache dann besser Schluß für heute.

(Noch'n Mist): Ist wohl durch ein Edit von user profile iconIhopeonlyReader überholt.


IhopeonlyReader - Mi 21.08.13 16:22

sorry :D ja ist überholt ;)


knittel - Mi 21.08.13 23:03

Die Lösung von Gammatester ist glaube ich wirklich die beste, das richtige Ergebnis liefern auf jeden Fall beide (Gammatesters und meine, die meisten anderen sehen etwas umständlich aus :gruebel: )

P.S. Hab mal nen Performance Check gemacht, Gammatesters Variante braucht nur 80% von der Zeit, die mein Befehl braucht, ist also ein gutes Stück schneller. Danke! :)


IhopeonlyReader - Do 22.08.13 00:26

Meins ist falsch? Was denn? Die Werte die ich getestet habe klappten :O