Seite 1 von 1

(math) Fast Sinus

Verfasst: 03.12.2005 03:02
von Kaeru Gaman
dieses posting wurde inspiriert von einem detail in diesem thread:
http://forums.purebasic.com/german/viewtopic.php?t=5856


für viele sachen braucht man eine sinus funktion.

dass ein array schneller ist, wurde ja schon oft erwähnt.

dann taucht das problem auf, wie groß das array sein soll.

wenn das 360 felder groß ist, muss ich den winkel begrenzen.
ich müsste also ein Modulo einbauen,
und das eventuelle negative vorzeichen eliminieren...

aber

kann ich das auch anders begrenzen, ohne modulo?
muss ein vollkreis 360° haben?

wenn ich den vollkreis nicht in 360 schritte,
sondern in 1024, 2048 oder 4096 schritte einteile, also eine 2er-potenz,
dann kann ich den modulo mit bitweisem AND bestimmen,
und spare tonnen von rechenzyklen.


also

im folgenden beispiel wird ein sinus-array erstellt, mit 4096 feldern.
damit wird der vollkreis beschrieben 0°->360°

(jeder der bruchrechnung kann, weiß nun, das jeder schritt im array 360 4096stel grad weitergeht)

in Zeile 16 wird der winkel in "arc" umgerechnet, in den index

winkel ist also ein winkel in 0-360 grad, arc ein winkel mit 0-4096 grad.

(für eine anwendung würde ich allerdings empfehlen, gleich mit einem 4096-grad-vollkreis zu arbeiten)

den sinus eines beliebigen winkels hole ich jetzt mit SinA( arc & 4095)

das ist praktisch das modulo...

den cosinus hole ich aus derselben tabelle mit SinA( (arc + 1024) & 4095)
der cosinus ist schließlich ein 90° phasenverschobener sinus.

das plotten im fenster dient nur dem beweis, das die rechnung wirklich "dicht" ist,
also, das mittels des AND kein index außerhalb des arrays angesprochen wird.

das +15 bei der winkelerhöhung dient dazu, zu zeigen, dass es genau ist,
dass also immer auf 24tel kreisen geplottet wird.
bitte beliebig verändern...

hier nun das listing:

Code: Alles auswählen

Dim SinA.f(4095)
#pi = 3.14159265

For n=0 To 4095
    SinA(n) = Sin(#pi / 2048 * n )      ; #pi als erstes, damit ein float übergeben wird
Next

winkel = 0

OpenWindow(0,0,0,800,600, #PB_Window_ScreenCentered | #PB_Window_SystemMenu, "Sinustest")

Repeat
    StartDrawing(WindowOutput())

        arc = winkel * 4096 / 360                       ; umrechnung winkel -> index

        xp = 400 + SinA( arc & 4095 ) * 250             ; sinus aus dem array
        yp = 300 + SinA( (arc+1024) & 4095 ) * 250      ; cosinus ist 90° verschoben

        FrontColor(Random(255),Random(255),Random(255)) ; zufallsfarbe
        Circle(xp,yp,12+Random(12))                     ; kreis plotten, zufallsradius

        winkel +15                                      ; mal hier mit beliebigen werten spielen
        If winkel > 36000 : winkel = -36000 : EndIf    ; hundert vollkreise plus und minus

    StopDrawing()
    
    Delay(0)
    EVT = WindowEvent()

Until EVT = #PB_Event_CloseWindow
ich werde nen thread für rückfragen extra öffnen, ich hätte schon gerne feedback...

Verfasst: 17.12.2006 13:05
von Kekskiller
Also wenn man so Speed- und Speicher-bewusst sein will, dann braucht man doch nur 90° vorberechnen. Schlussendlich muss man diese XY-Koordinaten - je nach dem in welchem Quadranten der Winkel liegt - nur noch auf -/+ setzen.

So spart man sich noch mehr Speicher, hat gleiche (oder noch mehr) Genauigkeit. Nur der Speed ist wohl ein bisserl geringer.

Verfasst: 17.12.2006 13:15
von Kaeru Gaman
das ist richtig.

ich habe deshalb ein 360°-Array verwendet, damit man so wenig wie möglich umrechnen muss.

der speicherverbrauch fällt meines erachtens nach nicht schwer ins gewicht.

ein Array mit Double und 131.072 Werten braucht grad mal ein einziges MB.
Peanuts!

(btw: es mag auffallen, das obiger code V3.94 ist)

Verfasst: 26.03.2007 15:14
von dell_jockey
Kaeru,

herzlichen Dank für diese Code! Anbei die 4.0 Anpassungen:

Code: Alles auswählen

; PB 3.x code by Kaeru Gaman - adapted to PB4.x

Dim SinA.f(4095) 

For n=0 To 4095 
    SinA(n) = Sin(#PI / 2048 * n )      ; #pi als erstes, damit ein float übergeben wird 
Next 

winkel = 0 

OpenWindow(0,0,0,800,600,  "Sinustest", #PB_Window_ScreenCentered | #PB_Window_SystemMenu) 

Repeat 
   StartDrawing(WindowOutput(0)) 
      arc = winkel * 4096 / 360                            ; umrechnung winkel -> index 
      xp = 400 + SinA( arc & 4095 ) * 250                  ; sinus aus dem array 
      yp = 300 + SinA( (arc+1024) & 4095 ) * 250           ; cosinus ist 90° verschoben 
      FrontColor(RGB(Random(255),Random(255),Random(255))) ; zufallsfarbe 
      Circle(xp,yp,12+Random(12))                          ; kreis plotten, zufallsradius 
      winkel +15                                           ; mal hier mit beliebigen werten spielen 
      If winkel > 36000 : winkel = -36000 : EndIf          ; hundert vollkreise plus und minus 
   StopDrawing()
   Delay(0) 
   EVT = WindowEvent() 
Until EVT = #PB_Event_CloseWindow

Verfasst: 26.03.2007 15:31
von Kaeru Gaman
freut mich, dass er dir nützt.
danke für die anpassung. :D

Verfasst: 23.02.2008 17:47
von dell_jockey
um dieses Thema erneut aufzuwärmen: für eine Anwendung habe ich das ganze ein bisl umgeschrieben und möchte das jetzt hier präsentieren:

Code: Alles auswählen

; ---------------------------------------------------
;              SIN-COS-tables.pbi
; ---------------------------------------------------
; precomputed global SIN/COS lookup tables are
; referenced by macros to reduce function call
; overhead and avoid trig conversion calculations
; - fSIN ( degrees )    ; fast sine
; - fCOS ( degrees )    ; fast cosine
; ---------------------------------------------------
; based on Kaeru Gaman's following PureBoard entry:
; http://www.purebasic.fr/german/viewtopic.php?t=5884
; ---------------------------------------------------

Global _Trig_Resolution_.w = 16384   ; steps around unity circle, needs
                                     ; to be a power of 2, i.e.
                                     ; 512, 1024, 2048, 4096, 8192, ...
                                     ; the larger this value, the larger
                                     ; the tables become, but one also
                                     ; gets more precision
                                     
Global _Resolution_minus_One_.w = _Trig_Resolution_.w - 1                                     

Global Dim SinTable.f(_Resolution_minus_One_.w)
Global Dim CosTable.f(_Resolution_minus_One_.w)

Global Angle_to_Index.f = ( _Trig_Resolution_ / 360.0 )

_Trig_Res_halved_.w = _Trig_Resolution_.w >> 1              ; populate tables
For n = 0 To _Resolution_minus_One_.w                        
    SinTable(n) = Sin( (#PI / _Trig_Res_halved_.w) * n )
    CosTable(n) = Cos( (#PI / _Trig_Res_halved_.w) * n )
Next 

Macro fSIN( degrees )
  SinTable( Int((Angle_to_Index.f * degrees) + 0.5 ) & (_Resolution_minus_One_.w) )
EndMacro

Macro fCOS( degrees )
  CosTable( Int((Angle_to_Index.f * degrees) + 0.5 ) & (_Resolution_minus_One_.w) )
EndMacro


; ------------------------------------------
; Usage
; ------------------------------------------
; set __UsageDefined__ to #True to test this
; file standalone
; ------------------------------------------
Define.b __UsageDefined__ = #True  
If __UsageDefined__
  For i = 0 To 359 Step 15
    Debug( "SIN(" + Str(i)+") = " + StrD( fSIN( i ), 6) )
    Debug( "COS(" + Str(i)+") = " + StrD( fCOS( i ), 6) ) 
  Next
EndIf

Verfasst: 06.06.2008 00:42
von Kaeru Gaman
war im Februar nicht da, sehe deinen Code jetzt erst per Zufall...

danke für deine Arbeit, sieht gut aus. :allright:

auch wenn die meilenlangen Underscores nicht mein Fall sind,
es ist dadurch tatsächlich allcompatibler...

Verfasst: 06.06.2008 21:29
von NicTheQuick
Ich würde allerdings keine Words, sondern Longs vorziehen, weil die schnell
sind.