Assembly code for sin(x) using Taylor expansion

后端 未结 4 1699
一整个雨季
一整个雨季 2021-02-05 15:53

In x86 Linux, how can I implement sin(x) in assembly code using Taylor Expansion?

相关标签:
4条回答
  • 2021-02-05 16:07

    Why? There is already a FCOS and FSIN opcode since the 80387 (circa 1987) processor

    source:

    http://ref.x86asm.net/coder32.html

    Wikipedia

    Personal friend from demo scene

    0 讨论(0)
  • 2021-02-05 16:08
    long double LD_COS(long double __X)
    {
        register long double __VALUE;
    
        __asm__ __volatile__(
            "fcos                  \n\t"
            : "=t" (__VALUE)
            : "0" (__X)
        );
        return __VALUE;
    }
    
    long double LD_SIN(long double __X)
    {
        register long double __VALUE;
    
        __asm__ __volatile__(
            "fsin                  \n\t"
            : "=t" (__VALUE)
            : "0" (__X)
        );
        return __VALUE;
    }
    
    0 讨论(0)
  • 2021-02-05 16:15

    You don't state which CPU architecture so I'm assuming x86.

    The simplist (and possibly most inefficient) way would be to write the formula in RPN, which can be mapped almost directly to FPU instructions.

    Example,

    algebraic formula : x - (x^3/3!) + (x^5/5!)

    RPN : x x x * x * 3 2 * / - x x * x * x * x * 5 4 * 3 * 2 * / +

    which becomes :

    fld x
    fld x
    fld x
    fmul
    fld x
    fmul
    fild [const_3]
    fild [const_2]
    fmul
    fdiv
    fsub
    fld x
    fld x
    fmul 
    fld x
    fmul
    fld x
    fmul
    fld x
    fmul
    fild [const_5]
    fild [const_4]
    fmul
    fild [const_3]
    fmul
    fild [const_2]
    fmul
    fdiv
    fadd
    

    There are some obvious optimisation strategies -

    • instead of calculating x, xxx, xxxxx etc for each term, store a 'running product' and just multiply by x*x each time
    • instead of calculating the factorial for each term, do the same 'running product'

    Here's some commented code for x86 FPU, the comments after each FPU instruction show the stack state after that instruction has executed, with the stack top (st0) on the left, eg :

    fldz ; 0
    fld1 ; 1, 0
    

    --snip--

    bits 32
    
    section .text
    
    extern printf
    extern atof
    extern atoi
    extern puts
    global main
    
    taylor_sin:
      push eax
      push ecx
    
      ; input :
      ;  st(0) = x, value to approximate sin(x) of
      ;  [esp+12] = number of taylor series terms
    
      ; variables we'll use :
      ; s = sum of all terms (final result)
      ; x = value we want to take the sin of
      ; fi = factorial index (1, 3, 5, 7, ...)
      ; fc = factorial current (1, 6, 120, 5040, ...)
      ; n = numerator of term (x, x^3, x^5, x^7, ...)
    
      ; setup state for each iteration (term)
      fldz ; s x
      fxch st1 ; x s
      fld1 ; fi x s
      fld1 ; fc fi x s
      fld st2 ; n fc fi x s
    
      ; first term
      fld st1 ; fc n fc fi x s
      fdivr st0,st1 ; r n fc fi x s
      faddp st5,st0 ; n fc fi x s
    
      ; loop through each term
      mov ecx,[esp+12] ; number of terms
      xor eax,eax ; zero add/sub counter
    
    loop_term:
      ; calculate next odd factorial
      fld1 ; 1 n fc fi x s
      faddp st3 ; n fc fi x s
      fld st2 ; fi n fc fi x s
      fmulp st2,st0
      fld1 ; 1 n fc fi x s
      faddp st3 ; n fc fi x s
      fld st2 ; fi n fc fi x s
      fmulp st2,st0 ; n fc fi x s
    
      ; calculate next odd power of x
      fmul st0,st3 ; n*x fc fi x s
      fmul st0,st3 ; n*x*x fc fi x s
    
      ; divide power by factorial
      fld st1 ; fc n fc fi x s
      fdivr st0,st1 ; r n fc fi x s
    
      ; check if we need to add or subtract this term
      test eax,1
      jnz odd_term
      fsubp st5,st0 ; n fc fi x s
      jmp skip
    odd_term:
      ; accumulate result
      faddp st5,st0 ; n fc fi x s
    skip:
      inc eax ; increment add/sub counter
      loop loop_term
    
      ; unstack work variables
      fstp st0
      fstp st0
      fstp st0
      fstp st0
    
      ; result is in st(0)
    
      pop ecx
      pop eax
    
      ret
    
    main:
    
      ; check if we have 2 command-line args
      mov eax, [esp+4]
      cmp eax, 3
      jnz error
    
      ; get arg 1 - value to calc sin of
      mov ebx, [esp+8]
      push dword [ebx+4]
      call atof
      add esp, 4
    
      ; get arg 2 - number of taylor series terms
      mov ebx, [esp+8]
      push dword [ebx+8]
      call atoi
      add esp, 4
    
      ; do the taylor series approximation
      push eax
      call taylor_sin
      add esp, 4
    
      ; output result
      sub esp, 8
      fstp qword [esp]
      push format
      call printf
      add esp,12
    
      ; return to libc
      xor eax,eax
      ret
    
    error:
      push error_message
      call puts
      add esp,4
      mov eax,1
      ret
    
    section .data
    
    error_message: db "syntax: <x> <terms>",0
    format: db "%0.10f",10,0
    

    running the program :

    $ ./taylor-sine 0.5 1
    0.4791666667
    $ ./taylor-sine 0.5 5
    0.4794255386
    $ echo "s(0.5)"|bc -l
    .47942553860420300027
    
    0 讨论(0)
  • 2021-02-05 16:17

    Would this article help you?

    http://www.coranac.com/2009/07/sines/

    It has a couple of algorithms for computing approximate sin(x) values, with both C and assembly versions. Granted, it's ARM assembly, but the gist of it should translate easily to x86 or similar.

    0 讨论(0)
提交回复
热议问题