sin関数の計算結果がGR-SAKURAと違う?

ド素人ですw。以前にGR-SAKURAのフォーラムで「8×8×8のLED-CUBEを作ったけど、うまく動かない」で助けて頂いた者です。

https://japan.renesasrulz.com/gr_user_forum_japanese/f/gr-sakura/3717/thread

同じLED-CUBEをとある事情で、SAKURA->秋月で買ったRX621ボードへの移植をしてます。(せっかくかわいいSAKURAボードは、他の用事に使おうかとw)

e2studioで作ってます。なんとかほぼ動くようになったのですが、sinを使ってる所でSAKURAとLEDの表示が違います…。

SAKURAの方のプログラムの該当部分は、

1 :  void ripples(int times, int repeat)
2 :  {
3 :    int i,j,k,z;
4 :    float distance,ztemp;
5 :    clearData();

6 :    for(k=0;k<=repeat;++k) {
7 :      for(i=0;i<=3;++i) {
8 :        for(j=0;j<=3;++j) {
9 :          distance=sqrt((3.5-(float)i)*(3.5-(float)i)+(3.5-(float)j)*(3.5-(float)j));
10:          ztemp=3.5+sin(distance/1.3+(float)k/3.14159)*3.5;
11:          z=(int)(ztemp+0.5);
12:          bitSet(dispData[z][i],j);
13:          bitSet(dispData[z][7-i],j);
14:          bitSet(dispData[z][i],7-j);
15:          bitSet(dispData[z][7-i],7-j);
16:        }
17:      }
18:      LEDDisp(times);
19:      clearData();
20:    }
21:  }

LED表示用配列の辺は見ないで頂いて(移植に伴いbitsetー>ビットシフトに変えてます)、9~11行目の式で、がじぇるねのwebコンパイラでビルドしたSAKURAと、e2studioでビルドしたRX621ボードとで、計算結果が異なる原因が何かあるでしょうか…?

sqrtとsinをsqrtfとsinfに変えてみたりとか、型指定を外してみたりとかしても、変わらない様です。

(printfで計算結果を確認しようとしたのですがうまくいかないので、ほんとに計算結果が違うかどうかは確認できていません…)

どなたかご助言頂けると助かります。

Parents
  • koheiさん&岡宮さん、こんにちは。NoMaYです。

    > repeatは、メインループからrepeat=250で呼び出してます。(sinの位相をずらしながら何回計算するかの繰り返し回数の指定です。)

    repeat=250 ということが分かった(3では全然足りないことが分かった)ので、これは repeat=250での以下の結果が欲しいかな、と思います。

    > ・GR-SAKURAで値を出力して、その結果を投稿して頂く。(ripples関数のrepeat引数の値も。3にしとけば良いのかな?)

    岡宮さんの手元でGR-SAKURA+CC-RXでのprintfが出来ていますので、出来れば、もうこちらも同じ値での結果が欲しいかな、とも思います。

    途中から一致しなくなる場合: (少なくともrepeat=3の範囲内では一致していた)

    ・途中から一致しなくなる原因を探す

    最後まで一致していた場合:

    ・koheiさんのアセンブルリストと私の手元のe2 studio+CC-RXのアセンブルリストが等価であることを確認した上で、repeat=250でシミュレータでデータ取得して値を比較する。もしアセンブルリストが等価とは考えられないものであったならばその原因を探す。

    ・値の比較結果が一致だったのであれば、koheiさんの手元でprintf出来るように模索する。(もう原因は別のところかも知れない可能性も高いかも知れない、、、)

    ・値の比較結果が不一致だったのであれば、その原因を探す。

    でしょうか、、、

  • repeatを150って書いたrepと250って書いたrepがありました、すみません。250の方が正解です。
    しかし、動画を見ると分かりますが(特にコマ送りすると)、最初:k=0から値が違うようです。250まで検証するのはきっと意味が無いと思います。
    fのついてないsqrtの定義は double sqrt(double d) と思うので、意図しない型変換が起こらない様に、distanceもztempもdobuleにして、定数も3.5D(でいいのかしら)でdoubleにして、走らせてみます。
    それでもダメなら、
    double t;
    t=(3.5D-i)*(3.5D-i)+(3.5D-i)*(3.5D-i);
    distance=sqrt(t);
    にしてみます。(すぐできるでしょ、と言われそうですが、明日チャレンジで勘弁して下さいw。)
  • さて、このプログラムは単なる「壮大なLチカ」で、変な事はしてないつもりなのですが、NoMayさんの言う、

    >プログラムをどんどん削って(そういうことが起きる可能性が極力小さくなるところまで削って)簡素化してやって、様子を見る

    をやるべきかと思いました。

    動画を見れば予想は付くかと思いますが、メインループは、

    main_loop.txt
    void main(void)
    {
    //    int k,i,j,z;
    //    float distance, ztemp;
    
    	wait_ms(500);
        PORTD.DDR.BYTE = 0xff;  // シリアルデータ出力portをoutputに
        PORTE.DDR.BYTE = 0xff;  // レイヤー選択出力portをoutputに
        PORTA.DDR.BYTE |= 1;  // ラッチクロック用pinをoutputに
        PORTA.DDR.BYTE |= 2;  // シフトクロック用pinをoutputに
    
    	for(;;) {
    		plane_x(8);//8
    		plane_y(8);//8
    		plane_z(8);//8
    
    		raindrop(15,60);
    //		ballbound(15,48);
    		boxexpand2(20);//20
    		boxexpand1(10);//10
    		rainrise(15,60);
    //		downstream(15,120);
    		sincurve(10);
    		letter1(15);
    		letter2(30);
    		ripples(10,250);
    	}
    }

    この様に、色々な表示パターンの関数を呼び出してます。

    まずripplesだけにして確認する、そして表示パターンの関数を1個ずつ活かしてやって、どの関数が悪さしてるのか犯人を突き止める、という作業に入りますw。

  • あっという間に犯人確定。「rainrise()」という関数を呼び出さなければ、ずっとちゃんと計算してくれます。

    rainrise.txt
    void shift_zminus()
    {
    	int i,j;
    	for(j=0;j<=6;++j) {
    		for(i=0;i<=7;++i) {
    			dispData[j][i] = dispData[j+1][i];
    		}
    	}
    	for(i=0;i<=7;++i) {
    		dispData[7][i] = 0;
    	}
    }
    
    void rainrise(int times,int repeat)
    {
    	int m,i,x,y;
    
    	clearData();
    
    	for(m=0;m<=repeat;++m) {
    		for(i=0;i<=7;++i) {
    			dispData[7][i] = 0;
    		}
    		for(i=0;i<=rand()*2/0x3fff+1;++i) {
    			x=rand()*7/0x3fff;
    			y=rand()*7/0x3fff;
    			dispData[7][y] |= 1<<x;
    		}
    		LEDDisp(times);
    		shift_zminus();
    	}
    }
    

    これは…はずかし。多分「何やってるんですか?rand()の使い方が全然間違ってます!」という事になりそうな…。

    1:z=7の面に1~3個のランダムな点を置く。

    2:dispData全体を、z=1ー>0、z=2ー>1、……z=7ー>6という風に上に1段ずらす。

    3:z=7の面を一旦全部消す。

    という流れで、「xとyに0~7のランダムな数字を入れる(これをランダムに1~3回やる)」というプログラムのつもりです。

     xとyが範囲外(7より大きい数字)になってて、dispData[7][y]に値を代入する時に、多分、「定義したdispDataの配列の外に値を書き込んでる」せいで、正しい計算に必要なRAMを書き換えちゃってるのでは?、という事ですよね?

    (そっくりな「raindrop」という関数もあるのですが、そちらは呼び出してもその後の計算に影響はない。これはdispData[0][y]に最初の点を書き込むので、yが7よりちょっとぐらい大きくても、たまたま想定してた変数として使っていい領域を超えてない、という事かしら?)

    質問変わりました。「rand()の使い方を教えて下さい」になりそうです…(皆様、壮大にご迷惑をお掛けしました。)

  • 概ね自己解決です。大変お騒がしました。

    x=rand()*7/0x3fff;
    y=rand()*7/0x3fff;

    の所を、

    x=((double)rand()-1)/RAND_MAX*8;
    y=((double)rand()-1)/RAND_MAX*8;


    に変更しました。

    randの前の(double)型指定は、無いと全部ゼロになってしうようでした。(型変換、難しいですね…)

    最初は(double)rand()/RAND_MAX×7にしてたのですが、7が全く出なかったので、「*8」にしたものの、rand()=RAND_MAXになった時にx=8になると、配列の範囲外(配列は7まで)になりそうなので、1引いて絶対にx=8にならないようにしてみましたが、どうなんでしょう…。(「1~3個配置する」の為のfor文の方も「((double)rand()-1)/RAND_MAX*3+1」に変更してます。)

    これで、ほぼ均等な1/8の確率で、0~7の数字が出る式でしょうか?

    (ちなみに、SAKURAの方は、

    x=random(8);
    y=random(8);

    としてました。)

  • > x=((double)rand()-1)/RAND_MAX*8;
    > y=((double)rand()-1)/RAND_MAX*8;
    >
    > に変更しました。

    rand() が稀な確率で 0 を返した場合結果が -1 になって宜しくないので

    x=(int)(rand()/((RAND_MAX+1)*8.0f));
    y=(int)(rand()/((RAND_MAX+1)*8.0f));

    の方が良いと思います。コストが問題になる場合は

    x=rand()%8;
    y=rand()%8;

    でも良いでしょう。
  • x=(int)(rand()/((RAND_MAX+1.0f)*8.0f));
    y=(int)(rand()/((RAND_MAX+1.0f)*8.0f));

    に訂正
  • x=(int)(rand()/(RAND_MAX+1.0f)*8.0f);
    y=(int)(rand()/(RAND_MAX+1.0f)*8.0f);

    再び訂正
  • repありがとです。マイナスになっても、(-1/RAND_MAX)*8->int型に代入されるときにきっとゼロになって問題ないだろうと思ったのですが、なるほど、必ず1より小さくする為に、分子を小さくせずに、分母を大きくしておく方ですね。
    知らない事がまだまだ多いなぁ~w。%は余りを求める演算子なんですね。答えは必ず整数になって型を気にしないでいいでしょうから、シンプルでいいですね。
  • いややっぱダメくさい。確認中。
  • #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        for (int i = 0; i < 1000000000; i++) {
            int x=(int)(rand()/(RAND_MAX+1.0f)*8.0f);
            if (x < 0 || x > 7) {
                printf("%d: x = %d\n", i, x);
                fflush(stdout);
            }
        }
    }    
    

    Wandboxで実行

    丸めにより 8 が出る模様

  • 案外面倒くさいこと判明

    #if (RAND_MAX+1U)%8==0
        x=(int)(rand()/((RAND_MAX+1U)/8));
        y=(int)(rand()/((RAND_MAX+1U)/8));
    #else
        x=(int)(rand()/((RAND_MAX+1U)/8+1));
        y=(int)(rand()/((RAND_MAX+1U)/8+1));
    #endif
    

    確認コード

    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX 7
    
    int main(void)
    {
        int count[MAX+1] = {0};
        for (int i = 0; i < 1000000000; i++) {
    #if (RAND_MAX+1U)%(MAX+1U)==0
            int x=(int)(rand()/((RAND_MAX+1U)/(MAX+1U)));
    #else
            int x=(int)(rand()/((RAND_MAX+1U)/(MAX+1U)+1));
    #endif
            if (x < 0 || x > MAX) {
                printf("Err %d: x = %d\n", i, x);
                fflush(stdout);
            } else {
                count[x]++;
            }
        }
        for (int i = 0; i &= MAX; i++) {
            printf("%d: %d\n", i, count[i]);
        }
    }
    

    Wandboxで実行

Reply
  • 案外面倒くさいこと判明

    #if (RAND_MAX+1U)%8==0
        x=(int)(rand()/((RAND_MAX+1U)/8));
        y=(int)(rand()/((RAND_MAX+1U)/8));
    #else
        x=(int)(rand()/((RAND_MAX+1U)/8+1));
        y=(int)(rand()/((RAND_MAX+1U)/8+1));
    #endif
    

    確認コード

    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX 7
    
    int main(void)
    {
        int count[MAX+1] = {0};
        for (int i = 0; i < 1000000000; i++) {
    #if (RAND_MAX+1U)%(MAX+1U)==0
            int x=(int)(rand()/((RAND_MAX+1U)/(MAX+1U)));
    #else
            int x=(int)(rand()/((RAND_MAX+1U)/(MAX+1U)+1));
    #endif
            if (x < 0 || x > MAX) {
                printf("Err %d: x = %d\n", i, x);
                fflush(stdout);
            } else {
                count[x]++;
            }
        }
        for (int i = 0; i &= MAX; i++) {
            printf("%d: %d\n", i, count[i]);
        }
    }
    

    Wandboxで実行

Children
No Data