#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+7;
const double pi=acos(-1.0);
struct Segtree
{
	double x,y,arc;
}s[maxn<<2];

inline void pushup(int rt)
{
    s[rt].x=s[rt<<1].x+s[rt<<1|1].x*cos(s[rt<<1].arc)-s[rt<<1|1].y*sin(s[rt<<1].arc);
    s[rt].y=s[rt<<1].y+s[rt<<1|1].x*sin(s[rt<<1].arc)+s[rt<<1|1].y*cos(s[rt<<1].arc);
    s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
}

inline void build(int rt,int l,int r)
{
    if(l==r)
    {
        s[rt].x=1.0;
        s[rt].y=0.0;
        s[rt].arc=0.0;
        return;
	}
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    pushup(rt);
}

inline void update1(int rt,int l,int r,int L,int R,double v)
{
    if (L<=l&&r<=R)
    {
        s[rt].x+=v*cos(s[rt].arc);
        s[rt].y+=v*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if (L<=mid) update1(rt<<1,l,mid,L,R,v);
    if (R>mid) update1(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

inline void update2(int rt,int l,int r,int L,int R,double v)
{
    if(L<=l&&r<=R)
    {
        double l=sqrt((s[rt].x*s[rt].x)+(s[rt].y*s[rt].y));
        s[rt].arc-=v*pi/180.0;
        s[rt].x=l*cos(s[rt].arc);
        s[rt].y=l*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if(L<=mid) update2(rt<<1,l,mid,L,R,v);
    if(R>mid) update2(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(register int i=1;i<=m;i++)
    {
        int op,x;
        double y;
        scanf("%d%d%lf",&op,&x,&y);
        if(op==1)update1(1,1,n,x,x,y);
        if(op==2)update2(1,1,n,x,x,y);
        printf("%.12f %.12f\n",s[1].x,s[1].y);
    }
    return 0;
}


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
#define maxn 300005
const double pi=acos(-1);
int n,m;
struct Segtree
{
	double x,y;//这一条线段相对于上一条线段，所在直线为x轴，以该线段起点为坐标原点的平面直角坐标系中的相对位置；
	double arc;//这条线段相当于上一条线段的角度
}s[maxn<<2];

inline void update(int rt)
{
	s[rt].x=s[rt<<1].x+s[rt<<1|1].x*cos(s[rt<<1].arc)-s[rt<<1|1].y*sin(s[rt<<1].arc);
	s[rt].y=s[rt<<1].y+s[rt<<1|1].x*sin(s[rt<<1].arc)+s[rt<<1|1].y*cos(s[rt<<1].arc);
	s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
}

inline void build(int rt,int l,int r)
{
	if(l==r)
	{
		s[rt].x=1.0;
		s[rt].y=0.0;
		s[rt].arc=0.0;
		return;
	}
	int mid=l+r>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	update(rt);
}

inline void modify1(int rt,int l,int r,int L,int R,double len)
{
	if(L<=l&&r<=R)
	{
		s[rt].x+=len*cos(s[rt].arc);
		s[rt].y+=len*sin(s[rt].arc);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid) modify1(rt<<1,l,mid,L,R,len);
	if(R>mid) modify1(rt<<1|1,mid+1,r,L,R,len);
	update(rt);
}

inline void modify2(int rt,int l,int r,int L,int R,double arc)
{
	if(L<=l&&r<=R)
	{
		double len=sqrt(s[rt].x*s[rt].x+s[rt].y*s[rt].y);
		s[rt].arc-=arc*pi/(double)180;
		s[rt].x=len*cos(s[rt].arc);
		s[rt].y=len*sin(s[rt].arc);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid) modify2(rt<<1,l,mid,L,R,arc);
	if(R>mid) modify2(rt<<1|1,mid+1,r,L,R,arc);
	update(rt);
}

int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	for(register int i=1;i<=m;i++)
	{
		int op,x;
		double y;
		scanf("%d%d%lf",&op,&x,&y);
		if(op==1) modify1(1,1,n,x,x,y);
		if(op==2) modify2(1,1,n,x,x,y);
		printf("%.10lf %.10lf\n",s[1].x,s[1].y);
	}
	return 0;
}


#include <bits/stdc++.h>
using namespace std;
const double PI=acos(-1.0);
#define maxn 300005
struct node
{
    int l,r;
    double x,y;
    double arc;
}s[maxn<<2];
int n,q;

void pushup(int rt)
{
    double r,arc;
    r=sqrt(s[rt<<1|1].x*s[rt<<1|1].x+s[rt<<1|1].y*s[rt<<1|1].y);
    arc=atan2(s[rt<<1|1].y,s[rt<<1|1].x);
    s[rt].x=s[rt<<1].x+r*cos(s[rt<<1].arc+arc);
    s[rt].y=s[rt<<1].y+r*sin(s[rt<<1].arc+arc);
    s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
    return;
}

void build(int rt,int l,int r)
{
    s[rt].l=l;
    s[rt].r=r;
    if(l+1==r)
    {
        s[rt].x=1.0;
        s[rt].y=0.0;
        s[rt].arc=0.0;
        return;
    }
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid,r);
    pushup(rt);
    return;
}

void update(int pos,double valr,double vala,int rt)
{
    double len,arc;
    if(s[rt].l+1==s[rt].r)
    {
        len=sqrt(s[rt].x*s[rt].x+s[rt].y*s[rt].y);
        arc=atan2(s[rt].y,s[rt].x);
        len+=valr,arc+=vala;
        s[rt].x=len*cos(arc);
        s[rt].y=len*sin(arc);
        s[rt].arc=arc;
        return;
    }
    if(pos<=s[rt<<1].r) update(pos,valr,vala,rt<<1);
    else update(pos,valr,vala,rt<<1|1);
    pushup(rt);
    return;
}

int main()
{
    scanf("%d%d",&n,&q);
    build(1,0,n);
    while(q--)
    {
    	int op,pos;
    	double val;
        scanf("%d%d%lf",&op,&pos,&val);
        if(op==1) update(pos,val,0.0,1);
        else update(pos,0.0,(1.0-val/360.0)*2*PI,1);
        printf("%.10f %.10f\n",s[1].x,s[1].y);
    }
    return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define PI acos(-1.0)

struct node
{
    int l;
    int r;
    double x;
    double y;
    double a;
}s[1200010];
int n;

void pushup(int cur)
{
    double r,a;
    r=sqrt(s[2*cur+1].x*s[2*cur+1].x+s[2*cur+1].y*s[2*cur+1].y);
    a=atan2(s[2*cur+1].y,s[2*cur+1].x);
    s[cur].x=s[2*cur].x+r*cos(s[2*cur].a+a);
    s[cur].y=s[2*cur].y+r*sin(s[2*cur].a+a);
    s[cur].a=s[2*cur].a+s[2*cur+1].a;
    return;
}

void build(int l,int r,int cur)
{
    int m;
    s[cur].l=l;
    s[cur].r=r;
    if(l+1==r)
    {
        s[cur].x=1.0;
        s[cur].y=0.0;
        s[cur].a=0.0;
        return;
    }
    m=(l+r)/2;
    build(l,m,2*cur);
    build(m,r,2*cur+1);
    pushup(cur);
    return;
}

void update(int tar,double valr,double vala,int cur)
{
    double r,a;
    if(s[cur].l+1==s[cur].r)
    {
        r=sqrt(s[cur].x*s[cur].x+s[cur].y*s[cur].y);
        a=atan2(s[cur].y,s[cur].x);
        r+=valr,a+=vala;
        s[cur].x=r*cos(a);
        s[cur].y=r*sin(a);
        s[cur].a=a;
        return;
    }
    if(tar<=s[2*cur].r) update(tar,valr,vala,2*cur);
    else update(tar,valr,vala,2*cur+1);
    pushup(cur);
    return;
}

int main()
{
    double val;
    int q,op,tar;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        build(0,n,1);
        while(q--)
        {
            scanf("%d%d%lf",&op,&tar,&val);
            if(op==1) update(tar,val,0.0,1);
            else update(tar,0.0,(1.0-val/360.0)*2*PI,1);
            printf("%.10f %.10f\n",s[1].x,s[1].y);
        }
    }
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+7;
const double pi=acos(-1.0);
struct Segtree
{
	double x,y,arc;
}s[maxn<<1];

inline void pushup(int rt)
{
    s[rt].x=s[rt<<1].x+s[rt<<1|1].x*cos(s[rt<<1].arc)-s[rt<<1|1].y*sin(s[rt<<1].arc);
    s[rt].y=s[rt<<1].y+s[rt<<1|1].x*sin(s[rt<<1].arc)+s[rt<<1|1].y*cos(s[rt<<1].arc);
    s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
}

inline void build(int rt,int l,int r)
{
    if(l==r)
    {
        s[rt].x=1.0;
        s[rt].y=0.0;
        s[rt].arc=0.0;
        return;
	}
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
}

inline void update1(int rt,int l,int r,int L,int R,double v)
{
    if (L<=l&&r<=R)
    {
        s[rt].x+=v*cos(s[rt].arc);
        s[rt].y+=v*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if (L<=mid) update1(rt<<1,l,mid,L,R,v);
    if (R>mid) update1(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

inline void update2(int rt,int l,int r,int L,int R,double v)
{
    if(L<=l&&r<=R)
    {
        double l=sqrt((s[rt].x*s[rt].x)+(s[rt].y*s[rt].y));
        s[rt].arc-=v*pi/180.0;
        s[rt].x=l*cos(s[rt].arc);
        s[rt].y=l*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if(L<=mid) update2(rt<<1,l,mid,L,R,v);
    if(R>mid) update2(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(register int i=1;i<=m;i++)
    {
        int op,x;
        double y;
        scanf("%d%d%lf",&op,&x,&y);
        if(op==1)update1(1,1,n,x,x,y);
        if(op==2)update2(1,1,n,x,x,y);
        printf("%.12f %.12f\n",s[1].x,s[1].y);
    }
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+7;
const double pi=acos(-1.0);
struct Segtree
{
	double x,y,arc;
}s[maxn<<1];

inline void pushup(int rt)
{
    s[rt].x=s[rt<<1].x+s[rt<<1|1].x*cos(s[rt<<1].arc)-s[rt<<1|1].y*sin(s[rt<<1].arc);
    s[rt].y=s[rt<<1].y+s[rt<<1|1].x*sin(s[rt<<1].arc)+s[rt<<1|1].y*cos(s[rt<<1].arc);
    s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
}

inline void build(int rt,int l,int r)
{
    if(l==r)
    {
        s[rt].x=1.0;
        s[rt].y=0.0;
        s[rt].arc=0.0;
        return;
	}
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    pushup(rt);
}

inline void update1(int rt,int l,int r,int L,int R,double v)
{
    if (L<=l&&r<=R)
    {
        s[rt].x+=v*cos(s[rt].arc);
        s[rt].y+=v*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if (L<=mid) update1(rt<<1,l,mid,L,R,v);
    if (R>mid) update1(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

inline void update2(int rt,int l,int r,int L,int R,double v)
{
    if(L<=l&&r<=R)
    {
        double l=sqrt((s[rt].x*s[rt].x)+(s[rt].y*s[rt].y));
        s[rt].arc-=v*pi/180.0;
        s[rt].x=l*cos(s[rt].arc);
        s[rt].y=l*sin(s[rt].arc);
        return;
    }
    int mid=l+r>>1;
    if(L<=mid) update2(rt<<1,l,mid,L,R,v);
    if(R>mid) update2(rt<<1|1,mid+1,r,L,R,v);
    pushup(rt);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(register int i=1;i<=m;i++)
    {
        int op,x;
        double y;
        scanf("%d%d%lf",&op,&x,&y);
        if(op==1)update1(1,1,n,x,x,y);
        if(op==2)update2(1,1,n,x,x,y);
        printf("%.12f %.12f\n",s[1].x,s[1].y);
    }
    return 0;
}


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
#define maxn 300005
const double pi=acos(-1);
int n,m;
struct Segtree
{
	double x,y;//这一条线段相对于上一条线段，所在直线为x轴，以该线段起点为坐标原点的平面直角坐标系中的相对位置；
	double arc;//这条线段相当于上一条线段的角度
}s[maxn];

inline void update(int rt)
{
	s[rt].x=s[rt<<1].x+s[rt<<1|1].x*cos(s[rt<<1].arc)-s[rt<<1|1].y*sin(s[rt<<1].arc);
	s[rt].y=s[rt<<1].y+s[rt<<1|1].x*sin(s[rt<<1].arc)+s[rt<<1|1].y*cos(s[rt<<1].arc);
	s[rt].arc=s[rt<<1].arc+s[rt<<1|1].arc;
}

inline void build(int rt,int l,int r)
{
	if(l==r)
	{
		s[rt].x=1.0;
		s[rt].y=0.0;
		s[rt].arc=0.0;
		return;
	}
	int mid=l+r>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	update(rt);
}

inline void modify1(int rt,int l,int r,int L,int R,double len)
{
	if(L<=l&&r<=R)
	{
		s[rt].x+=len*cos(s[rt].arc);
		s[rt].y+=len*sin(s[rt].arc);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid) modify1(rt<<1,l,mid,L,R,len);
	if(R>mid) modify1(rt<<1|1,mid+1,r,L,R,len);
	update(rt);
}

inline void modify2(int rt,int l,int r,int L,int R,double arc)
{
	if(L<=l&&r<=R)
	{
		double len=sqrt(s[rt].x*s[rt].x+s[rt].y*s[rt].y);
		s[rt].arc-=arc*pi/(double)180;
		s[rt].x=len*cos(s[rt].arc);
		s[rt].y=len*sin(s[rt].arc);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid) modify2(rt<<1,l,mid,L,R,arc);
	if(R>mid) modify2(rt<<1|1,mid+1,r,L,R,arc);
	update(rt);
}

int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	for(register int i=1;i<=m;i++)
	{
		int op,x;
		double y;
		scanf("%d%d%lf",&op,&x,&y);
		if(op==1) modify1(1,1,n,x,x,y);
		if(op==2) modify2(1,1,n,x,x,y);
		printf("%.10lf %.10lf\n",s[1].x,s[1].y);
	}
	return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef pair<int,P> P1;
#define pb push_back
#define mp make_pair
#define eps 1e-9
#define INF 1000000000
#define fi first
#define sc second
#define mod 1000000007
complex<double>seg[(1<<20)],lazy[(1<<20)];
double L[300005];
void update(){
	for(int i=(1<<19)-1;i<(1<<20);i++){
		seg[i] = complex<double>(1.0,0.0);
	}
	for(int i=(1<<19)-2;i>=0;i--){
		seg[i] = seg[i*2+1]+seg[i*2+2];
	}
}
complex<double> update(int a,int b,int k,int l,int r,complex<double>val){
	if(r<a || b<l) return seg[k];
	if(a<=l && r<=b){
		seg[k] *= val;
		lazy[k] *= val;
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return seg[k] = update(a,b,k*2+1,l,(l+r)/2,val) + update(a,b,k*2+2,(l+r)/2+1,r,val);
}
complex<double> query(int a,int b,int k,int l,int r){
	if(r<a || b<l) return complex<double>(0.0,0.0);
	if(a<=l && r<=b){
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return query(a,b,k*2+1,l,(l+r)/2) + query(a,b,k*2+2,(l+r)/2+1,r);
}
int n,q;
int main(){
	cin >> n >> q;
	fill(lazy,lazy+(1<<20),complex<double>(1.0,0.0));
	fill(L,L+300005,1.0);
	update();
	for(int i=0;i<q;i++){
		int t; scanf("%d",&t);
		if(t == 1){
			int p; double z; scanf("%d%lf",&p,&z);
			update(p,p,0,0,(1<<19)-1,complex<double>( (L[p]+z)/L[p], 0.0));
			L[p] += z;
		}
		else{
			int p; double z; scanf("%d%lf",&p,&z);
			z = z/180.0*3.14159265358979323846;
			update(p,n,0,0,(1<<19)-1,polar(1.0,-z));
		}
		complex<double>ret = query(1,n,0,0,(1<<19)-1);
		printf("%.15f %.15f\n",ret.real(),ret.imag());
	}
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef pair<int,P> P1;
#define pb push_back
#define mp make_pair
#define eps 1e-9
#define INF 1000000000
#define fi first
#define sc second
#define mod 1000000007
complex<double>seg[(1<<20)],lazy[(1<<20)];
double L[300005];
void update(){
	for(int i=(1<<19)-1;i<(1<<20)-1;i++){
		seg[i] = complex<double>(1.0,0.0);
	}
	for(int i=(1<<19)-2;i>=0;i--){
		seg[i] = seg[i*2+1]+seg[i*2+2];
	}
}
complex<double> update(int a,int b,int k,int l,int r,complex<double>val){
	if(r<a || b<l) return seg[k];
	if(a<=l && r<=b){
		seg[k] *= val;
		lazy[k] *= val;
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return seg[k] = update(a,b,k*2+1,l,(l+r)/2,val) + update(a,b,k*2+2,(l+r)/2+1,r,val);
}
complex<double> query(int a,int b,int k,int l,int r){
	if(r<a || b<l) return complex<double>(0.0,0.0);
	if(a<=l && r<=b){
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return query(a,b,k*2+1,l,(l+r)/2) + query(a,b,k*2+2,(l+r)/2+1,r);
}
int n,q;
int main(){
	cin >> n >> q;
	fill(lazy,lazy+(1<<20),complex<double>(1.0,0.0));
	fill(L,L+300005,1.0);
	update();
	for(int i=0;i<q;i++){
		int t; scanf("%d",&t);
		if(t == 1){
			int p; double z; scanf("%d%lf",&p,&z);
			update(p,p,0,0,(1<<19)-1,complex<double>( (L[p]+z)/L[p], 0.0));
			L[p] += z;
		}
		else{
			int p; double z; scanf("%d%lf",&p,&z);
			z = z/180.0*3.141592653589;
			update(p,n,0,0,(1<<19)-1,polar(1.0,-z));
		}
		complex<double>ret = query(1,n,0,0,(1<<19)-1);
		printf("%.10f %.10f\n",ret.real(),ret.imag());
	}
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef pair<int,P> P1;
#define pb push_back
#define mp make_pair
#define eps 1e-9
#define INF 1000000000
#define fi first
#define sc second
#define mod 1000000007
complex<double>seg[(1<<20)],lazy[(1<<20)];
double L[300005];
void update(){
	for(int i=(1<<19)-1;i<(1<<20)-1;i++){
		seg[i] = complex<double>(1.0,0.0);
	}
	for(int i=(1<<19)-2;i>=0;i--){
		seg[i] = seg[i*2+1]+seg[i*2+2];
	}
}
complex<double> update(int a,int b,int k,int l,int r,complex<double>val){
	if(r<a || b<l) return seg[k];
	if(a<=l && r<=b){
		seg[k] *= val;
		lazy[k] *= val;
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return seg[k] = update(a,b,k*2+1,l,(l+r)/2,val) + update(a,b,k*2+2,(l+r)/2+1,r,val);
}
complex<double> query(int a,int b,int k,int l,int r){
	if(r<a || b<l) return complex<double>(0.0,0.0);
	if(a<=l && r<=b){
		return seg[k];
	}
	seg[k*2+1] *= lazy[k];
	seg[k*2+2] *= lazy[k];
	lazy[k*2+1] *= lazy[k];
	lazy[k*2+2] *= lazy[k];
	lazy[k] = complex<double>(1.0,0.0);
	return query(a,b,k*2+1,l,(l+r)/2) + query(a,b,k*2+2,(l+r)/2+1,r);
}
int n,q;
int main(){
	cin >> n >> q;
	fill(lazy,lazy+(1<<20),complex<double>(1.0,0.0));
	fill(L,L+300005,1.0);
	update();
	for(int i=0;i<q;i++){
		int t; scanf("%d",&t);
		if(t == 1){
			int p; double z; scanf("%d%lf",&p,&z);
			update(p,p,0,0,(1<<19)-1,complex<double>( (L[p]+z)/L[p], 0.0));
			L[p] += z;
		}
		else{
			int p; double z; scanf("%d%lf",&p,&z);
			z = z/180.0*3.141592653589;
			update(p,n,0,0,(1<<19)-1,polar(1.0,-z));
		}
		complex<double>ret = query(1,n,0,0,(1<<19)-1);
		printf("%.15f %.15f\n",ret.real(),ret.imag());
	}
}


#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <set>
#include <utility>
#include <queue>
#include <map>
#include <cmath>
using namespace std;

typedef double db;
const int maxn = 300000+10;
const db pi = acos(-1.0);
const db eps = 1e-9;
int n, m;
#define md ((l + r) >> 1)
#define lson (cur << 1)
#define rson (cur << 1 | 1)
db sqr(db x) {
	return x * x;
}
struct point {
	db x, y;
	point(){}
	point(db _x, db _y)  {
		x = _x;
		y = _y;
	}
	point operator -( point o){
		return point(x - o.x, y - o.y);
	}
	point operator +( point o) {
		return point(x + o.x, y + o.y);
	}
	point operator *(db k) {
		return point(x * k, y * k);
	}
	db len() {
		return sqrt(sqr(x) + sqr(y));
	}
	void setlen(db l) {
		db k = l / len();
		x *= k, y *= k;
	}
	point rot(db a) {
		return point(x * cos(a) - y * sin(a), x * sin(a) + y * cos(a));
	}
}B[maxn << 2], zero;
db A[maxn << 2];

void trans(int cur, db a, point move) {
	// printf("now = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);
	B[cur] = B[cur].rot(a);
	B[cur] = B[cur] + move;
	A[cur] += a;
		// printf("after = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);

}

void pull(int cur) {
	//if (fabs(A[cur]) < eps && fabs(B[cur].x) < eps && fabs(B[cur].y) < eps) return;
	trans(lson, A[cur], B[cur]);
	trans(rson, A[cur], B[cur]);
	A[cur] = 0;
	B[cur] = point(0., 0.);
}

void build(int cur, int l, int r)  {
	if (l == r) {
		A[cur] = 0.;
		B[cur] = point((db)l, 0.);
		// printf("l=%d, cur=%d, (%.5lf,%.5lf)\n",l,cur,B[cur].x,B[cur].y);
		return;
	}
	A[cur]=0; B[cur]=point(0,0);
	build(lson, l, md);
	build(rson, md + 1, r);
}

void modify(int cur, int l, int r, int ql, int qr, db a, point move)  {
	if (ql <=l && r <= qr) {
		trans(cur, a, move);
		return;
	}
	pull(cur);
	if (ql <= md) modify(lson, l, md, ql, qr, a, move);
	if (qr > md) modify(rson, md + 1, r, ql, qr, a, move);
}

point query(int cur, int l, int r, int pos) {
	if (l == r) return B[cur];
	pull(cur);
	if (pos <= md) return query(lson, l, md, pos);
	else return query(rson, md + 1, r, pos);
}

int main()  {
	zero = point(0, 0);
	scanf("%d%d", &n, &m);
	build(1, 0, n);
	for (int i = 1; i <= m; i ++) {
		int t, x, y;
		scanf("%d%d%d", &t, &x, &y);
		if (t == 1) {
			point p1=query(1,0,n,x);
			point p2=query(1,0,n,x-1);
			// printf("x=%d (%.5lf,%.5lf) (%.5lf,%.5lf)\n",
				// x, p1.x,p1.y,p2.x,p2.y);
			point dir = query(1, 0, n, x) - query(1, 0, n, x - 1);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);

			dir.setlen(y);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);
			modify(1, 0, n, x, n, 0, dir);
		}else {
			point o = query(1, 0, n, x - 1);
			db a = - y / 360.0 * 2 * pi;
			point dir = point( - o.x * cos(a) + o.y * sin(a) + o.x,
						 - o.x * sin(a) - o.y * cos(a) + o.y);
			modify(1, 0, n, x, n, a, dir);
		}
		point ans = query(1, 0, n, n);
		printf("%.9lf %.9lf\n", (double)ans.x, (double)ans.y);
	}
	return 0;
}


#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <set>
#include <utility>
#include <queue>
#include <map>
#include <cmath>
using namespace std;

typedef long double db;
const int maxn = 300000+10;
const db pi = acos(-1.0);
const db eps = 1e-11;
int n, m;
#define md ((l + r) >> 1)
#define lson (cur << 1)
#define rson (cur << 1 | 1)
db sqr(db x) {
	return x * x;
}
struct point {
	db x, y;
	point(){}
	point(db _x, db _y)  {
		x = _x;
		y = _y;
	}
	point operator -( point o){
		return point(x - o.x, y - o.y);
	}
	point operator +( point o) {
		return point(x + o.x, y + o.y);
	}
	point operator *(db k) {
		return point(x * k, y * k);
	}
	db len() {
		return sqrt(sqr(x) + sqr(y));
	}
	void setlen(db l) {
		db k = l / len();
		x *= k, y *= k;
	}
	point rot(db a) {
		return point(x * cos(a) - y * sin(a), x * sin(a) + y * cos(a));
	}
}B[maxn << 2], zero;
db A[maxn << 2];

void trans(int cur, db a, point move) {
	// printf("now = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);
	B[cur] = B[cur].rot(a);
	B[cur] = B[cur] + move;
	A[cur] += a;
		// printf("after = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);

}

void pull(int cur) {
	if (fabs(A[cur]) < eps && fabs(B[cur].x) < eps && fabs(B[cur].y) < eps) return;
	trans(lson, A[cur], B[cur]);
	trans(rson, A[cur], B[cur]);
	A[cur] = 0;
	B[cur] = point(0, 0);
}

void build(int cur, int l, int r)  {
	if (l == r) {
		A[cur] = 0.;
		B[cur] = point((db)l, 0.);
		// printf("l=%d, cur=%d, (%.5lf,%.5lf)\n",l,cur,B[cur].x,B[cur].y);
		return;
	}
	A[cur]=0; B[cur]=point(0,0);
	build(lson, l, md);
	build(rson, md + 1, r);
}

void modify(int cur, int l, int r, int ql, int qr, db a, point move)  {
	if (ql <= l && r <= qr) {
		trans(cur, a, move);
		return;
	}
	pull(cur);
	if (ql <= md) modify(lson, l, md, ql, qr, a, move);
	if (qr > md) modify(rson, md + 1, r, ql, qr, a, move);
}

point query(int cur, int l, int r, int pos) {
	if (l == r) return B[cur];
	pull(cur);
	if (pos <= md) return query(lson, l, md, pos);
	else return query(rson, md + 1, r, pos);
}

int main()  {
	zero = point(0, 0);
	scanf("%d%d", &n, &m);
	build(1, 0, n);
	for (int i = 1; i <= m; i ++) {
		int t, x, y;
		scanf("%d%d%d", &t, &x, &y);
		if (t == 1) {
			point p1=query(1,0,n,x);
			point p2=query(1,0,n,x-1);
			// printf("x=%d (%.5lf,%.5lf) (%.5lf,%.5lf)\n",
				// x, p1.x,p1.y,p2.x,p2.y);
			point dir = query(1, 0, n, x) - query(1, 0, n, x - 1);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);

			dir.setlen(y);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);
			modify(1, 0, n, x, n, 0, dir);
		}else {
			point o = query(1, 0, n, x - 1);
			db a = (360 - y) / (db)360 * 2 * pi;
			point dir = point( - o.x * cos(a) + o.y * sin(a) + o.x,
						 - o.x * sin(a) - o.y * cos(a) + o.y);
			modify(1, 0, n, x, n, a, dir);
		}
		point ans = query(1, 0, n, n);
		printf("%.9lf %.9lf\n", (double)ans.x, (double)ans.y);
	}
	return 0;
}


#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <set>
#include <utility>
#include <queue>
#include <map>
#include <cmath>
using namespace std;

typedef long double db;
const int maxn = 300000+10;
const db pi = acos(-1.0);
const db eps = 1e-9;
int n, m;
#define md ((l + r) >> 1)
#define lson (cur << 1)
#define rson (cur << 1 | 1)
db sqr(db x) {
	return x * x;
}
struct point {
	db x, y;
	point(){}
	point(db _x, db _y)  {
		x = _x;
		y = _y;
	}
	point operator -( point o){
		return point(x - o.x, y - o.y);
	}
	point operator +( point o) {
		return point(x + o.x, y + o.y);
	}
	point operator *(db k) {
		return point(x * k, y * k);
	}
	db len() {
		return sqrt(sqr(x) + sqr(y));
	}
	void setlen(db l) {
		db k = l / len();
		x *= k, y *= k;
	}
	point rot(db a) {
		return point(x * cos(a) - y * sin(a), x * sin(a) + y * cos(a));
	}
}B[maxn << 2], zero;
db A[maxn << 2];

void trans(int cur, db a, point move) {
	// printf("now = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);
	B[cur] = B[cur].rot(a);
	B[cur] = B[cur] + move;
	A[cur] += a;
		// printf("after = %.5lf,(%.5lf,%.5lf)\n", A[cur], B[cur].x,B[cur].y);

}

void pull(int cur) {
	//if (fabs(A[cur]) < eps && fabs(B[cur].x) < eps && fabs(B[cur].y) < eps) return;
	trans(lson, A[cur], B[cur]);
	trans(rson, A[cur], B[cur]);
	A[cur] = 0;
	B[cur] = point(0., 0.);
}

void build(int cur, int l, int r)  {
	if (l == r) {
		A[cur] = 0.;
		B[cur] = point((db)l, 0.);
		// printf("l=%d, cur=%d, (%.5lf,%.5lf)\n",l,cur,B[cur].x,B[cur].y);
		return;
	}
	A[cur]=0; B[cur]=point(0,0);
	build(lson, l, md);
	build(rson, md + 1, r);
}

void modify(int cur, int l, int r, int ql, int qr, db a, point move)  {
	if (ql <=l && r <= qr) {
		trans(cur, a, move);
		return;
	}
	pull(cur);
	if (ql <= md) modify(lson, l, md, ql, qr, a, move);
	if (qr > md) modify(rson, md + 1, r, ql, qr, a, move);
}

point query(int cur, int l, int r, int pos) {
	if (l == r) return B[cur];
	pull(cur);
	if (pos <= md) return query(lson, l, md, pos);
	else return query(rson, md + 1, r, pos);
}

int main()  {
	zero = point(0, 0);
	scanf("%d%d", &n, &m);
	build(1, 0, n);
	for (int i = 1; i <= m; i ++) {
		int t, x, y;
		scanf("%d%d%d", &t, &x, &y);
		if (t == 1) {
			point p1=query(1,0,n,x);
			point p2=query(1,0,n,x-1);
			// printf("x=%d (%.5lf,%.5lf) (%.5lf,%.5lf)\n",
				// x, p1.x,p1.y,p2.x,p2.y);
			point dir = query(1, 0, n, x) - query(1, 0, n, x - 1);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);

			dir.setlen(y);
			// printf("dir=%.5lf,%.5lf\n",dir.x,dir.y);
			modify(1, 0, n, x, n, 0, dir);
		}else {
			point o = query(1, 0, n, x - 1);
			db a = - y / 360.0 * 2 * pi;
			point dir = point( - o.x * cos(a) + o.y * sin(a) + o.x,
						 - o.x * sin(a) - o.y * cos(a) + o.y);
			modify(1, 0, n, x, n, a, dir);
		}
		point ans = query(1, 0, n, n);
		printf("%.9lf %.9lf\n", (double)ans.x, (double)ans.y);
	}
	return 0;
}


/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 300010;
const double Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w;
}
struct Vec{
	double l,t;
	Vec operator + (const Vec& X) const{
		Vec res;
		double x_1 = l*cos(t);
		double y_1 = l*sin(t);
		double x_2 = X.l*cos(X.t);
		double y_2 = X.l*sin(X.t);
		res.l = sqrt(sqr(x_1+x_2)+sqr(y_1+y_2));
		res.t = atan2(y_1+y_2,x_1+x_2);
		return res;
	}
}v[N<<2];
int n,m,x,y,opt;
double tag[N<<2];
inline void pushup(int u){
	v[u] = v[u<<1]+v[u<<1|1];
}
void build(int u, int l, int r){
	if(l == r){
		v[u] = (Vec){1,0};
		return;
	}
	int mid = (l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
inline void pushdown(int u){
	v[u<<1].t += tag[u];
	v[u<<1|1].t += tag[u];
	tag[u<<1] += tag[u];
	tag[u<<1|1] += tag[u];
	tag[u] = 0;
}
void modify_l(int u, int l, int r, int x, int k){
	if(l == r){
		v[u].l += k;
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	if(x <= mid) modify_l(u<<1,l,mid,x,k);
	else modify_l(u<<1|1,mid+1,r,x,k);
	pushup(u);
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
}
void modify_t(int u, int l, int r, int x, int y, double k){
	if(r < x || l > y) return;
	if(x <= l && r <= y){
		v[u].t += k;
		tag[u] += k;
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	modify_t(u<<1,l,mid,x,y,k);
	modify_t(u<<1|1,mid+1,r,x,y,k);
	pushup(u);
}
int main(){
	// freopen("file.in","r",stdin);
	n = read(), m = read();
	build(1,1,n);
	for(int i = 1; i <= m; ++i){
		opt = read(), x = read(), y = read();
		if(opt == 1){
			modify_l(1,1,n,x,y);
		}else{
			modify_t(1,1,n,x,n,-y*Pi/180.0);
		}
		printf("%.12f %.12f\n",v[1].l*cos(v[1].t),v[1].l*sin(v[1].t));
	}
	return 0;
}


/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 300010;
const long double Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w;
}
struct Vec{
	long double l,t;
	Vec operator + (const Vec& X) const{
		Vec res;
		long double x_1 = l*cos(t);
		long double y_1 = l*sin(t);
		long double x_2 = X.l*cos(X.t);
		long double y_2 = X.l*sin(X.t);
		res.l = sqrt(sqr(x_1+x_2)+sqr(y_1+y_2));
		res.t = atan2(y_1+y_2,x_1+x_2);
		return res;
	}
}v[N<<2];
int n,m,x,y,opt;
long double tag[N<<2];
inline void pushup(int u){
	v[u] = v[u<<1]+v[u<<1|1];
}
void build(int u, int l, int r){
	if(l == r){
		v[u] = (Vec){1,0};
		return;
	}
	int mid = (l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
inline void pushdown(int u){
	v[u<<1].t += tag[u];
	v[u<<1|1].t += tag[u];
	tag[u<<1] += tag[u];
	tag[u<<1|1] += tag[u];
	tag[u] = 0;
}
void modify_l(int u, int l, int r, int x, int k){
	if(l == r){
		v[u].l += k;
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	if(x <= mid) modify_l(u<<1,l,mid,x,k);
	else modify_l(u<<1|1,mid+1,r,x,k);
	pushup(u);
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
}
void modify_t(int u, int l, int r, int x, int y, long double k){
	if(r < x || l > y) return;
	if(x <= l && r <= y){
		v[u].t += k;
		tag[u] += k;
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	modify_t(u<<1,l,mid,x,y,k);
	modify_t(u<<1|1,mid+1,r,x,y,k);
	pushup(u);
}
int main(){
	// freopen("file.in","r",stdin);
	n = read(), m = read();
	build(1,1,n);
	for(int i = 1; i <= m; ++i){
		opt = read(), x = read(), y = read();
		if(opt == 1){
			modify_l(1,1,n,x,y);
		}else{
			modify_t(1,1,n,x,n,-y/180.0*Pi);
		}
		printf("%.12f %.12f\n",(double)(v[1].l*cos(v[1].t)),(double)(v[1].l*sin(v[1].t)));
	}
	return 0;
}


/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define double long double
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 300010;
const double Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w;
}
struct Vec{
	double l,t;
	Vec operator + (const Vec& X) const{
		Vec res;
		double x_1 = l*cos(t);
		double y_1 = l*sin(t);
		double x_2 = X.l*cos(X.t);
		double y_2 = X.l*sin(X.t);
		res.l = sqrt(sqr(x_1+x_2)+sqr(y_1+y_2));
		res.t = atan2(y_1+y_2,x_1+x_2);
		return res;
	}
}v[N<<2];
int n,m,x,y,opt;
double tag[N<<2];
inline void pushup(int u){
	v[u] = v[u<<1]+v[u<<1|1];
}
void build(int u, int l, int r){
	if(l == r){
		v[u] = (Vec){1,0};
		return;
	}
	int mid = (l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
inline void pushdown(int u){
	v[u<<1].t += tag[u];
	v[u<<1|1].t += tag[u];
	tag[u<<1] += tag[u];
	tag[u<<1|1] += tag[u];
	tag[u] = 0;
}
void modify_l(int u, int l, int r, int x, int k){
	if(l == r){
		v[u].l += k;
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	if(x <= mid) modify_l(u<<1,l,mid,x,k);
	else modify_l(u<<1|1,mid+1,r,x,k);
	pushup(u);
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
}
void modify_t(int u, int l, int r, int x, int y, double k){
	if(r < x || l > y) return;
	if(x <= l && r <= y){
		v[u].t += k;
		tag[u] += k;
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	modify_t(u<<1,l,mid,x,y,k);
	modify_t(u<<1|1,mid+1,r,x,y,k);
	pushup(u);
}
int main(){
	// freopen("file.in","r",stdin);
	n = read(), m = read();
	build(1,1,n);
	for(int i = 1; i <= m; ++i){
		opt = read(), x = read(), y = read();
		if(opt == 1){
			modify_l(1,1,n,x,y);
		}else{
			modify_t(1,1,n,x,n,-y*Pi/180.0);
		}
		printf("%.12Lf %.12Lf\n",v[1].l*cos(v[1].t),v[1].l*sin(v[1].t));
	}
	return 0;
}


/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 300010;
const double Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w;
}
struct Vec{
	double l,t;
	Vec operator + (const Vec& X) const{
		Vec res;
		double x_1 = l*cos(t);
		double y_1 = l*sin(t);
		double x_2 = X.l*cos(X.t);
		double y_2 = X.l*sin(X.t);
		res.l = sqrt(sqr(x_1+x_2)+sqr(y_1+y_2));
		res.t = atan2(y_1+y_2,x_1+x_2);
		return res;
	}
}v[N<<2];
int n,m,x,y,opt;
double tag[N<<2];
inline void pushup(int u){
	v[u] = v[u<<1]+v[u<<1|1];
}
void build(int u, int l, int r){
	if(l == r){
		v[u] = (Vec){1,0};
		return;
	}
	int mid = (l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
inline void pushdown(int u){
	v[u<<1].t += tag[u];
	v[u<<1|1].t += tag[u];
	tag[u<<1] += tag[u];
	tag[u<<1|1] += tag[u];
	tag[u] = 0;
}
void modify_l(int u, int l, int r, int x, int k){
	if(l == r){
		v[u].l += k;
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	if(x <= mid) modify_l(u<<1,l,mid,x,k);
	else modify_l(u<<1|1,mid+1,r,x,k);
	pushup(u);
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
}
void modify_t(int u, int l, int r, int x, int y, double k){
	if(r < x || l > y) return;
	if(x <= l && r <= y){
		v[u].t += k;
		tag[u] += k;
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	modify_t(u<<1,l,mid,x,y,k);
	modify_t(u<<1|1,mid+1,r,x,y,k);
	pushup(u);
}
int main(){
	// freopen("file.in","r",stdin);
	n = read(), m = read();
	build(1,1,n);
	for(int i = 1; i <= m; ++i){
		opt = read(), x = read(), y = read();
		if(opt == 1){
			modify_l(1,1,n,x,y);
		}else{
			// printf("%.10f\n",-y*Pi/180.0);
			modify_t(1,1,n,x,n,-y*Pi/180.0);
		}
		printf("%.12f %.12f\n",v[1].l*cos(v[1].t),v[1].l*sin(v[1].t));
	}
	return 0;
}


/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <cmath>
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 300010;
const double Pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); return x*w;
}
struct Vec{
	double l,t;
	Vec operator + (const Vec& X) const{
		Vec res;
		double x_1 = l*cos(t);
		double y_1 = l*sin(t);
		double x_2 = X.l*cos(X.t);
		double y_2 = X.l*sin(X.t);
		res.l = sqrt(sqr(x_1+x_2)+sqr(y_1+y_2));
		res.t = atan2(y_1+y_2,x_1+x_2);
		return res;
	}
}v[N<<2];
int n,m,x,y,opt;
double tag[N<<2];
inline void pushup(int u){
	v[u] = v[u<<1]+v[u<<1|1];
}
void build(int u, int l, int r){
	if(l == r){
		v[u] = (Vec){1,0};
		return;
	}
	int mid = (l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
inline void pushdown(int u){
	v[u<<1].t += tag[u];
	v[u<<1|1].t += tag[u];
	tag[u<<1] += tag[u];
	tag[u<<1|1] += tag[u];
	tag[u] = 0;
}
void modify_l(int u, int l, int r, int x, int k){
	if(l == r){
		v[u].l += k;
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	if(x <= mid) modify_l(u<<1,l,mid,x,k);
	else modify_l(u<<1|1,mid+1,r,x,k);
	pushup(u);
	// printf("u=%d l=%d r=%d (%.3f %.3f)\n",u,l,r,v[u].l,v[u].t);
}
void modify_t(int u, int l, int r, int x, int y, double k){
	if(r < x || l > y) return;
	if(x <= l && r <= y){
		v[u].t += k;
		tag[u] += k;
		return;
	}
	pushdown(u);
	int mid = (l+r)>>1;
	modify_t(u<<1,l,mid,x,y,k);
	modify_t(u<<1|1,mid+1,r,x,y,k);
	pushup(u);
}
int main(){
	// freopen("file.in","r",stdin);
	n = read(), m = read();
	build(1,1,n);
	for(int i = 1; i <= m; ++i){
		opt = read(), x = read(), y = read();
		if(opt == 1){
			modify_l(1,1,n,x,y);
		}else{
			modify_t(1,1,n,x,n,-y/180.0*Pi);
		}
		printf("%.12f %.12f\n",v[1].l*cos(v[1].t),v[1].l*sin(v[1].t));
	}
	return 0;
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        return PT(cos(a) * x - sin(a) * y, sin(a) * x + cos(a) * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

typedef PT Value;
typedef PT Data;
typedef int Lazy;
struct Treap {
    static Treap mem[300003], *pmem;
    Treap *l, *r;
    int pri, key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(rand()), key(_key), val(1, 0), data(1, 0) {}
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
            if (l->lazy < 0) l->lazy += 360;
        }
        if (r) {
            r->lazy += lazy;
            if (r->lazy < 0) r->lazy += 360;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= z;
            if (right->lazy < 0) right->lazy += 360;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            mid->val *= (mid->val.len() + z) / mid->val.len();
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->data.rot(treap->lazy);
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        double c = cos(a), s = sin(a);
        return PT(c * x - s * y, s * x + c * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

typedef PT Value;
typedef PT Data;
typedef double Lazy;
struct Treap {
    static Treap mem[300003], *pmem;
    Treap *l, *r;
    int pri, key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(rand()), key(_key), val(1, 0), data(1, 0) {}
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
            //if (l->lazy < 0) l->lazy += 360;
        }
        if (r) {
            r->lazy += lazy;
            //if (r->lazy < 0) r->lazy += 360;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= z * PI / 180;
            //if (right->lazy < 0) right->lazy += 360;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            mid->val *= (mid->val.len() + z) / mid->val.len();
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->data.rot(treap->lazy);
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        double c = cos(a), s = sin(a);
        return PT(c * x - s * y, s * x + c * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

typedef PT Value;
typedef PT Data;
typedef double Lazy;
struct Treap {
    static Treap mem[300003], *pmem;
    Treap *l, *r;
    int pri, key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(rand()), key(_key), val(1, 0), data(1, 0) {}
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
            //if (l->lazy < 0) l->lazy += 360;
        }
        if (r) {
            r->lazy += lazy;
            //if (r->lazy < 0) r->lazy += 360;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    srand(time(0));
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= PI / 180 * z;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            double l = mid->val.len();
            mid->val *= (l + z) / l;
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->getData();
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

//template<class Data, class Lazy>
typedef pair<double,double> Data;
typedef double Lazy;

struct SegmentTree {
    // static Data identity;
    int n;
    int size;
    vector<Data> dat;
    vector<Lazy> lazy;

    int real_size;
    SegmentTree(int n_) {
        real_size = n_;
        n = 1;
        size = n_;
        while(n < n_) n *= 2;
        dat = vector<Data>(2*n-1);
        lazy = vector<Lazy>(2*n-1);
        init(0, 0, n);
    }

    void init(int k, int l, int r) {
        if(r - l == 1) {
            if (l < real_size) {
                // TODO set dat[k] of leaf at position l
                dat[k] = MP(1.0, 0.0);
            }
        } else {
            // non-leaf
            int m = (l+r)/2;
            init(chl(k), l, m);
            init(chr(k), m, r);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }
    void update(int a, int b, const Lazy &d) {
        update(a, b+1, 0, 0, n, d);
    }

    void propagate(int k, int l, int r) {
        // update dat[k] according to lazy value d
        double t = lazy[k];
        double x = dat[k].X * cos(t) + dat[k].Y * sin(t);
        double y = -dat[k].X * sin(t) + dat[k].Y * cos(t);
        dat[k] = MP(x, y);
        if (chr(k) < lazy.size()) {
            lazy[chl(k)] += lazy[k];
            lazy[chr(k)] += lazy[k];
        }
        lazy[k] = Lazy();
    }

    void update(int a, int b, int k, int l, int r, const Lazy &d) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            lazy[k] = d;
            propagate(k, l, r);
        } else {
            int m = (l+r)/2;
            update(a, b, chl(k), l, m, d);
            update(a, b, chr(k), m, r, d);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update2(int k, int z) {
        k += n - 1;
        double l = sqrt(dat[k].X*dat[k].X+dat[k].Y*dat[k].Y);
        dat[k].X *= (l+z)/l;
        dat[k].Y *= (l+z)/l;
        while(k > 0) {
            k = (k - 1) / 2;
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    Data query(int a, int b) {
        return query(a, b+1, 0, 0, n);
    }

    Data query(int a, int b, int k, int l, int r) {
        // if [a, b) and [l, r) are disjoint
        if(r <= a || b <= l) return MP(0.0, 0.0);
        propagate(k, l, r);
        // if [l, r) contains [a, b)
        if(a <= l && r <= b) {
            return dat[k];
        } else {
            Data vl = query(a, b, chl(k), l, (l + r) / 2);
            Data vr = query(a, b, chr(k), (l + r) / 2, r);
            return combine(vl, vr);
        }
    }

    Data combine(Data vl, Data vr) {
        return MP(vl.X + vr.X, vl.Y + vr.Y);
    }
};
const double pi = acos(-1);

int main()
{
    DRI(N, M);
    SegmentTree st(N+1);
    REP(i, M) {
        DRI(x);
        if (x == 2) {
            DRI(y, z);
            st.update(y, N, z * 1.0 / 360 * 2 * pi);
        } else {
            DRI(y, z);
            st.update2(y, z);
        }
        auto ans = st.query(1, N);
        printf("%.10f %.10f\n", ans.X, ans.Y);
    }
}


// Create your own template by modifying this file!
#include <ctime>
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        double c = cos(a), s = sin(a);
        return PT(c * x - s * y, s * x + c * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

typedef PT Value;
typedef PT Data;
typedef double Lazy;
struct Treap {
    static Treap mem[300003], *pmem;
    Treap *l, *r;
    int pri, key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(rand()), key(_key), val(1, 0), data(1, 0) {}
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
            //if (l->lazy < 0) l->lazy += 360;
        }
        if (r) {
            r->lazy += lazy;
            //if (r->lazy < 0) r->lazy += 360;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    srand(time(0));
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= PI / 180 * z;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            double l = mid->val.len();
            mid->val *= (l + z) / l;
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->getData();
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

//template<class Data, class Lazy>
typedef pair<double,double> Data;
typedef double Lazy;

const double pi = acos(-1);
struct SegmentTree {
    // static Data identity;
    int n;
    int size;
    vector<Data> dat;
    vector<Lazy> lazy;

    int real_size;
    SegmentTree(int n_) {
        real_size = n_;
        n = 1;
        size = n_;
        while(n < n_) n *= 2;
        dat = vector<Data>(2*n-1);
        lazy = vector<Lazy>(2*n-1);
        init(0, 0, n);
    }

    void init(int k, int l, int r) {
        if(r - l == 1) {
            if (l < real_size) {
                // TODO set dat[k] of leaf at position l
                dat[k] = MP(1.0, 0.0);
            }
        } else {
            // non-leaf
            int m = (l+r)/2;
            init(chl(k), l, m);
            init(chr(k), m, r);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }
    void update(int a, int b, const Lazy &d) {
        update(a, b+1, 0, 0, n, d);
    }

    void propagate(int k, int l, int r) {
        // update dat[k] according to lazy value d
        double t = lazy[k];
        double x = dat[k].X * cos(t) + dat[k].Y * sin(t);
        double y = -dat[k].X * sin(t) + dat[k].Y * cos(t);
        dat[k] = MP(x, y);
        if (chr(k) < lazy.size()) {
            lazy[chl(k)] += lazy[k];
            if (lazy[chl(k)] >= pi * 2) lazy[chl(k)] -= pi * 2;
            lazy[chr(k)] += lazy[k];
            if (lazy[chr(k)] >= pi * 2) lazy[chr(k)] -= pi * 2;
        }
        lazy[k] = 0.0;
    }

    void update(int a, int b, int k, int l, int r, const Lazy &d) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            lazy[k] = d;
            propagate(k, l, r);
        } else {
            int m = (l+r)/2;
            update(a, b, chl(k), l, m, d);
            update(a, b, chr(k), m, r, d);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update(int a, int k, int l, int r, int z) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || a+1 <= l) return;
        if (a <= l && r <= a+1) {
            assert(l == a && k == a + n-1);
            double len = sqrt(dat[k].X*dat[k].X+dat[k].Y*dat[k].Y);
            dat[k].X *= (len+z)/len;
            dat[k].Y *= (len+z)/len;
        } else {
            int m = (l+r)/2;
            update(a, chl(k), l, m, z);
            update(a, chr(k), m, r, z);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update(int a, int z) {
        update(a, 0, 0, n, z);
        //k += n - 1;
        //double l = sqrt(dat[k].X*dat[k].X+dat[k].Y*dat[k].Y);
        //dat[k].X *= (l+z)/l;
        //dat[k].Y *= (l+z)/l;
        //while(k > 0) {
            //k = (k - 1) / 2;
            //dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        //}
    }

    Data query(int a, int b) {
        return query(a, b+1, 0, 0, n);
    }

    Data query(int a, int b, int k, int l, int r) {
        // if [a, b) and [l, r) are disjoint
        if(r <= a || b <= l) return MP(0.0, 0.0);
        propagate(k, l, r);
        // if [l, r) contains [a, b)
        if(a <= l && r <= b) {
            return dat[k];
        } else {
            Data vl = query(a, b, chl(k), l, (l + r) / 2);
            Data vr = query(a, b, chr(k), (l + r) / 2, r);
            return combine(vl, vr);
        }
    }

    Data combine(Data vl, Data vr) {
        return MP(vl.X + vr.X, vl.Y + vr.Y);
    }
};

int main()
{
    DRI(N, M);
    SegmentTree st(N+1);
    REP(i, M) {
        DRI(x);
        if (x == 2) {
            DRI(y, z);
            st.update(y, N, z * 1.0 / 360 * 2 * pi);
        } else {
            DRI(y, z);
            st.update(y, z);
        }
        auto ans = st.query(1, N);
        printf("%.10f %.10f\n", ans.X, ans.Y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

//template<class Data, class Lazy>
typedef pair<double,double> Data;
typedef double Lazy;

const double pi = acos(-1);
struct SegmentTree {
    // static Data identity;
    int n;
    int size;
    vector<Data> dat;
    vector<Lazy> lazy;

    int real_size;
    SegmentTree(int n_) {
        real_size = n_;
        n = 1;
        size = n_;
        while(n < n_) n *= 2;
        dat = vector<Data>(2*n-1);
        lazy = vector<Lazy>(2*n-1);
        init(0, 0, n);
    }

    void init(int k, int l, int r) {
        if(r - l == 1) {
            if (l < real_size) {
                // TODO set dat[k] of leaf at position l
                dat[k] = MP(1.0, 0.0);
            }
        } else {
            // non-leaf
            int m = (l+r)/2;
            init(chl(k), l, m);
            init(chr(k), m, r);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }
    void update(int a, int b, const Lazy &d) {
        update(a, b+1, 0, 0, n, d);
    }

    void propagate(int k, int l, int r) {
        // update dat[k] according to lazy value d
        double t = lazy[k];
        double x = dat[k].X * cos(t) + dat[k].Y * sin(t);
        double y = -dat[k].X * sin(t) + dat[k].Y * cos(t);
        dat[k] = MP(x, y);
        if (chr(k) < lazy.size()) {
            lazy[chl(k)] += lazy[k];
            if (lazy[chl(k)] >= pi * 2) lazy[chl(k)] -= pi * 2;
            lazy[chr(k)] += lazy[k];
            if (lazy[chr(k)] >= pi * 2) lazy[chr(k)] -= pi * 2;
        }
        lazy[k] = 0.0;
    }

    void update(int a, int b, int k, int l, int r, const Lazy &d) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            lazy[k] = d;
            propagate(k, l, r);
        } else {
            int m = (l+r)/2;
            update(a, b, chl(k), l, m, d);
            update(a, b, chr(k), m, r, d);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update(int a, int b, int k, int l, int r, int z) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            double len = sqrt(dat[k].X*dat[k].X+dat[k].Y*dat[k].Y);
            dat[k].X *= (len+z)/len;
            dat[k].Y *= (len+z)/len;
        } else {
            int m = (l+r)/2;
            update(a, b, chl(k), l, m, z);
            update(a, b, chr(k), m, r, z);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update(int a, int z) {
        update(a, a+1, 0, 0, n, z);
    }

    Data query(int a, int b) {
        return query(a, b+1, 0, 0, n);
    }

    Data query(int a, int b, int k, int l, int r) {
        // if [a, b) and [l, r) are disjoint
        if(r <= a || b <= l) return MP(0.0, 0.0);
        propagate(k, l, r);
        // if [l, r) contains [a, b)
        if(a <= l && r <= b) {
            return dat[k];
        } else {
            Data vl = query(a, b, chl(k), l, (l + r) / 2);
            Data vr = query(a, b, chr(k), (l + r) / 2, r);
            return combine(vl, vr);
        }
    }

    Data combine(Data vl, Data vr) {
        return MP(vl.X + vr.X, vl.Y + vr.Y);
    }
};

int main()
{
    DRI(N, M);
    SegmentTree st(N+1);
    REP(i, M) {
        DRI(x);
        if (x == 2) {
            DRI(y, z);
            st.update(y, N, z * 1.0 / 360 * 2 * pi);
        } else {
            DRI(y, z);
            st.update(y, z);
        }
        auto ans = st.query(1, N);
        printf("%.10f %.10f\n", ans.X, ans.Y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        return PT(cos(a) * x - sin(a) * y, sin(a) * x + cos(a) * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

#define POOL 100005
typedef PT Value;
typedef PT Data;
typedef int Lazy;
struct Treap {
    static Treap mem[POOL], *pmem;
    Treap *l, *r;
    int pri, key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(rand()), key(_key), val(1, 0), data(1, 0) {}
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
            if (l->lazy < 0) l->lazy += 360;
        }
        if (r) {
            r->lazy += lazy;
            if (r->lazy < 0) r->lazy += 360;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= z;
            if (right->lazy < 0) right->lazy += 360;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            mid->val *= (mid->val.len() + z) / mid->val.len();
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->data.rot(treap->lazy);
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <ctime>
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

const double PI = acos(-1);
struct PT {
    double x, y;
    PT() {}
    PT(double xx, double yy): x(xx), y(yy) {}
    PT operator+(const PT& p) const {
        return PT(x + p.x, y + p.y);
    }
    PT& operator+=(const PT& p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    PT operator-(const PT& p) const {
        return PT(x - p.x, y - p.y);
    }
    PT operator*(double c) const {
        return PT(x * c, y * c);
    }
    PT& operator*=(double c) {
        x *= c;
        y *= c;
        return *this;
    }
    PT operator/(double c) const {
        return PT(x / c, y / c);
    }
    double operator*(const PT& p) const {
        return x * p.x + y * p.y;
    }
    double operator^(const PT& p) const {
        return x * p.y - y * p.x;
    }
    double len2() const {
        return x * x + y * y;
    }
    double len() const {
        return sqrt(len2());
    }
    PT U() const {
        return *this / len();
    }
    PT T() const {
        return PT(-y, x);
    }
    PT rot(double a) const {
        double c = cos(a), s = sin(a);
        return PT(c * x - s * y, s * x + c * y);
    }
    PT rot(int a) const {
        return rot(a * PI / 180);
    }
};

typedef PT Value;
typedef PT Data;
typedef double Lazy;
struct Treap {
    static Treap mem[300003], *pmem;
    static unsigned state;
    Treap *l, *r;
    unsigned pri;
    int key;
    Value val;
    Data data;
    Lazy lazy;
    Treap() {}
    Treap(int _key) : l(nullptr), r(nullptr), pri(state), key(_key), val(1, 0), data(1, 0) {
        state = state * 1297 + 1301;
    }
    void pull() {
        data = val;
        if (l) data += l->getData();
        if (r) data += r->getData();
    }
    Data getData() {
        return data.rot(lazy);
    }
    void push() {
        val = val.rot(lazy);
        data = data.rot(lazy);
        if (l) {
            l->lazy += lazy;
        }
        if (r) {
            r->lazy += lazy;
        }
        lazy = 0;
    }
} Treap::mem[], *Treap::pmem = Treap::mem;
unsigned Treap::state;

Treap *make(int key) {
    return new (Treap::pmem++) Treap(key);
}

Treap *merge(Treap *a, Treap *b) {
    if (!a || !b) return a ? a : b;
    if (a->pri > b->pri) {
        a->push();
        a->r = merge(a->r, b);
        a->pull();
        return a;
    } else {
        b->push();
        b->l = merge(a, b->l);
        b->pull();
        return b;
    }
}

void split(Treap *t, int k, Treap *&a, Treap *&b) {
    if (!t) a = b = nullptr;
    else if (t->key <= k) {
        a = t;
        a->push();
        split(t->r, k, a->r, b);
        a->pull();
    } else {
        b = t;
        b->push();
        split(t->l, k, a, b->l);
        b->pull();
    }
}

int main()
{
    srand(time(0));
    Treap::state = rand();
    DRI(N, M);
    Treap *treap = nullptr;
    REP1(i, 1, N) {
        treap = merge(treap, make(i));
    }

    REP(i, M) {
        DRI(x, y, z);
        if (x == 2) {
            Treap *left, *right;
            split(treap, y-1, left, right);
            right->lazy -= PI / 180 * z;
            treap = merge(left, right);
        } else {
            Treap *left, *mid, *right;
            split(treap, y-1, left, right);
            split(right, y, mid, right);
            assert(!mid->l && !mid->r);
            double l = mid->val.len();
            mid->val *= (l + z) / l;
            mid->pull();
            treap = merge(left, merge(mid, right));
        }
        auto ans = treap->getData();
        printf("%.10f %.10f\n", ans.x, ans.y);
    }
}


// Create your own template by modifying this file!
#include <string>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <deque>
#include <stack>
#include <functional>
#define X first
#define Y second
#define MAX(x, a) x = max(x, a)
#define MIN(x, a) x = min(x, a)
#define CASET int ___T; scanf("%d ", &___T); while (___T-- > 0)
#define SZ(X) ((int)(X).size())
#define LEN(X) strlen(X)
#define FOR(i,c) for(auto &i: c)
#define ALL(x) (x).begin(),(x).end()
#define REP(i,n) for(int i=0;i<(n);i++)
#define REP1(i,a,b) for(int i=(a);i<=(b);i++)
#define REPL(i,x) for(int i=0;x[i];i++)
#define PER(i,n) for(int i=(n)-1;i>=0;i--)
#define PER1(i,a,b) for(int i=(a);i>=(b);i--)
#define RI1(x) scanf("%d",&x)
#define RI2(x,y) RI1(x), RI1(y)
#define RI3(x,y...) RI1(x), RI2(y)
#define RI4(x,y...) RI1(x), RI3(y)
#define RI5(x,y...) RI1(x), RI4(y)
#define RI6(x,y...) RI1(x), RI5(y)
#define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define RI(argv...) GET_MACRO(argv, RI6, RI5, RI4, RI3, RI2, RI1)(argv)
#define DRI(argv...) int argv;RI(argv)
#define RS(x) scanf("%s",x)
#define PI(x) printf("%d\n",x)
#define PIS(x) printf("%d ",x)
#define DRL(x) LL x; RL(x)
#ifdef ONLINE_JUDGE
    #define PL(x) printf("%I64d\n",x)
    #define RL(x) scanf("%I64d\n",&x)
#else
    #define PL(x) printf("%lld\n",x)
    #define RL(x) scanf("%lld\n",&x)
#endif
#define MP make_pair
#define PB push_back
#define EB emplace_back
#define BG begin()
#define ED end()
#define PQ priority_queue
#define MS0(x) memset(x,0,sizeof(x))
#define MS1(x) memset(x,-1,sizeof(x))
#define SEP(x) ((x)?'\n':' ')
#define DRA(A, N) VI A(N); REP(i, N) RI(A[i])
using namespace std;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef long long LL;
const int INF = 1000000000;
template<typename T>
using V = std::vector<T>;

#ifdef DEBUG
     #define debug(args...)            {dbg,args; cerr<<endl;}
#else
    #define debug(args...)              // Just strip off all debug tokens
#endif

struct debugger
{
    template<typename T> debugger& operator , (const T& v)
    {
        cerr<<v<<" ";
        return *this;
    }
} dbg;
#include <vector>
using namespace std;

#define chl(k) (k*2+1)
#define chr(k) (k*2+2)

//template<class Data, class Lazy>
typedef pair<double,double> Data;
typedef double Lazy;

const double pi = acos(-1);
struct SegmentTree {
    // static Data identity;
    int n;
    int size;
    vector<Data> dat;
    vector<Lazy> lazy;

    int real_size;
    SegmentTree(int n_) {
        real_size = n_;
        n = 1;
        size = n_;
        while(n < n_) n *= 2;
        dat = vector<Data>(2*n-1);
        lazy = vector<Lazy>(2*n-1);
        init(0, 0, n);
    }

    void init(int k, int l, int r) {
        if(r - l == 1) {
            if (l < real_size) {
                // TODO set dat[k] of leaf at position l
                dat[k] = MP(1.0, 0.0);
            }
        } else {
            // non-leaf
            int m = (l+r)/2;
            init(chl(k), l, m);
            init(chr(k), m, r);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }
    void update(int a, int b, const Lazy &d) {
        update(a, b+1, 0, 0, n, d);
    }

    void propagate(int k, int l, int r) {
        // update dat[k] according to lazy value d
        double t = lazy[k];
        double x = dat[k].X * cos(t) + dat[k].Y * sin(t);
        double y = -dat[k].X * sin(t) + dat[k].Y * cos(t);
        dat[k] = MP(x, y);
        if (chr(k) < lazy.size()) {
            lazy[chl(k)] += lazy[k];
            if (lazy[chl(k)] >= pi * 2) lazy[chl(k)] -= pi * 2;
            lazy[chr(k)] += lazy[k];
            if (lazy[chr(k)] >= pi * 2) lazy[chr(k)] -= pi * 2;
        }
        lazy[k] = 0.0;
    }

    void update(int a, int b, int k, int l, int r, const Lazy &d) {
        // we have to make sure dat[k] is the right value after update
        propagate(k, l, r);
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            lazy[k] = d;
            propagate(k, l, r);
        } else {
            int m = (l+r)/2;
            update(a, b, chl(k), l, m, d);
            update(a, b, chr(k), m, r, d);
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    void update2(int k, int z) {
        k += n - 1;
        double l = sqrt(dat[k].X*dat[k].X+dat[k].Y*dat[k].Y);
        dat[k].X *= (l+z)/l;
        dat[k].Y *= (l+z)/l;
        while(k > 0) {
            k = (k - 1) / 2;
            dat[k] = combine(dat[chl(k)], dat[chr(k)]);
        }
    }

    Data query(int a, int b) {
        return query(a, b+1, 0, 0, n);
    }

    Data query(int a, int b, int k, int l, int r) {
        // if [a, b) and [l, r) are disjoint
        if(r <= a || b <= l) return MP(0.0, 0.0);
        propagate(k, l, r);
        // if [l, r) contains [a, b)
        if(a <= l && r <= b) {
            return dat[k];
        } else {
            Data vl = query(a, b, chl(k), l, (l + r) / 2);
            Data vr = query(a, b, chr(k), (l + r) / 2, r);
            return combine(vl, vr);
        }
    }

    Data combine(Data vl, Data vr) {
        return MP(vl.X + vr.X, vl.Y + vr.Y);
    }
};

int main()
{
    DRI(N, M);
    SegmentTree st(N+1);
    REP(i, M) {
        DRI(x);
        if (x == 2) {
            DRI(y, z);
            st.update(y, N, z * 1.0 / 360 * 2 * pi);
        } else {
            DRI(y, z);
            st.update2(y, z);
        }
        auto ans = st.query(1, N);
        printf("%.10f %.10f\n", ans.X, ans.Y);
    }
}