重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

【GOLF me!】初月無料お試し

下記のプログラムを作ったのですが、
Math.PI / 180 の部分は先に計算しておいたほうが処理が
早くなると言われたのですがそうなのでしょうか?
先に掛け算をしないといけないような気がするのですが。

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import java.lang.Math;


public class Test9 {

public static void main(String[] args) {

int r = (args.length > 0)? Integer.parseInt(args[0]):100;
int n = (args.length > 1)? Integer.parseInt(args[1]):16;

int x, y, x1, y1;

try {
BufferedImage image=new BufferedImage(r*2+10,r*2+10,BufferedImage.TYPE_INT_RGB);
Graphics2D g2d=image.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0,0,r*2+10,r*2+10);
g2d.setColor(Color.BLACK);

for ( double i = 0.0; i < 360.0; i += 360.0 / n ) {

x1 = (int) ( r * Math.cos( i * Math.PI / 180 ) );
y1 = (int) ( r * Math.sin( i * Math.PI / 180 ) );

for( double j = i + 360 / n; j < 360.0; j += 360.0 / n ) {
x = (int) ( r * Math.cos( j * Math.PI / 180 ) );
y = (int) ( r * Math.sin( j * Math.PI / 180 ) );

g2d.drawLine( x1 + r + 5, y1 * (-1) + r + 5, x + r + 5, y * (-1) + r + 5 );


}

}

ImageIO.write(image, "JPEG", new File("c:\\test9.jpg"));
} catch(Exception e) { e.printStackTrace(); }
}
}

A 回答 (3件)

確かに (j * Math.PI / 180)の中の*,/演算のどちらを先に実行するかで結果の最下位ビット等が異なる場合もあるかも知れません。


しかしそれは他にもプログラム内に出てくる短精度/倍精度の演算全てについて言える事で、特定の計算ステップを抜き出して問題にしても始まらないのではと思われます。
(全体として短(倍)精度で十分だとの判断がある筈なので)

drawLine()の処理がずっと重いので、Math.PI / 180の部分を別の定数に置き換えてもトータルの実行時間には殆ど影響しないでしょう。

それと定数同士の割り算が(複数回)出てきますが、多分コンパイル時に最適化処理の一環として割り算結果を別の乗数として出力したりすると思われますので、この点からもそのままでも問題無いと思われます。
(結果的には同じ筈ですが、Math.PI / 180.0 と書く方が良いのではと思われます)
    • good
    • 0
この回答へのお礼

ありがとうございました。

お礼日時:2013/06/27 00:55

結論から言うと、rの値を極端に大きな値にしないならMath.PI / 180を先に計算する誤差はまずプログラムの結果に影響しないと思います。

また、やはり浮動小数点演算は遅いので、Math.PI / 180を先に計算するとその分の計算を省ける分だけ性能が良くなります。

i * Math.PI / 180の部分しか見ていませんが、Math.PI / 180が先にあるとiの結果によらずMath.PI / 180の計算ができるので、少なくともJITされたあとのコードではMath.PI / 180を計算したあとの定数値としてそこが扱われると思います。先に置かないでも、i * (Math.PI / 180)で同じ事になるでしょう。当然、ループ内での浮動小数点演算の回数が大幅に減るので、その分性能が向上します。
ただ、質問者もお分かりの通り、その場合の誤差は現状より大きくなります。

計算の精度を犠牲にしても高速化が必要ならi * (Math.PI / 180)とすると思いますが、何れにしても盲目的に高速化するのではなくちゃんと計測して効果を理解した上でその変更をするか決めるべきでしょう。例えば、こんな感じに改造して実行してみたらどれくらい高速化したかわかると思います。
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import java.lang.Math;

public class FloatTest {

/**
* @param args
*/
public static void main(String[] args) {

int r = 100;
int n = 16;

int x, y, x1, y1;

try {
BufferedImage image = new BufferedImage(r * 2 + 10, r * 2 + 10,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, r * 2 + 10, r * 2 + 10);
g2d.setColor(Color.BLACK);
long startTime = System.currentTimeMillis();
for (int cnt = 0; cnt < 10000; cnt++) {
for (double i = 0.0; i < 360.0; i += 360.0 / n) {

x1 = (int) (r * Math.cos(i * (Math.PI / 180)));
y1 = (int) (r * Math.sin(i * (Math.PI / 180)));

for (double j = i + 360 / n; j < 360.0; j += 360.0 / n) {
x = (int) (r * Math.cos(j * (Math.PI / 180)));
y = (int) (r * Math.sin(j * (Math.PI / 180)));

g2d.drawLine(x1 + r + 5, y1 * (-1) + r + 5, x + r + 5,
y * (-1) + r + 5);

}

}
}
long estimatedTime = System.currentTimeMillis() - startTime;
System.out.println("Elapsed time:" + estimatedTime);
// ImageIO.write(image, "JPEG", new File("c:\\test9.jpg"));
} catch (Exception e) {
e.printStackTrace();
}
}
}

ついでに、誤差でどの程度結果が変わるかも調べてみましょう。
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import java.lang.Math;

public class FloatTest {

/**
* @param args
*/
public static void main(String[] args) {

int r = 100;
int n = 16;

int x, y, x1, y1;

try {
BufferedImage image = new BufferedImage(r * 2 + 10, r * 2 + 10,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, r * 2 + 10, r * 2 + 10);
g2d.setColor(Color.BLACK);

double diff2 = 0;

for (double i = 0.0; i < 360.0; i += 360.0 / n) {

x1 = (int) (r * Math.cos(i * Math.PI / 180));
y1 = (int) (r * Math.sin(i * Math.PI / 180));

diff2 += Math.pow(
((int) (r * Math.cos(i * (Math.PI / 180))) - x1), 2);
diff2 += Math.pow(
((int) (r * Math.sin(i * (Math.PI / 180))) - y1), 2);

for (double j = i + 360 / n; j < 360.0; j += 360.0 / n) {
x = (int) (r * Math.cos(j * Math.PI / 180));
y = (int) (r * Math.sin(j * Math.PI / 180));

diff2 += Math.pow(
((int) (r * Math.cos(j * (Math.PI / 180))) - x), 2);
diff2 += Math.pow(
((int) (r * Math.sin(j * (Math.PI / 180))) - y), 2);

g2d.drawLine(x1 + r + 5, y1 * (-1) + r + 5, x + r + 5, y
* (-1) + r + 5);
}

}
System.out.println("sigma=" + Math.sqrt(diff2));
// ImageIO.write(image, "JPEG", new File("c:\\test9.jpg"));
} catch (Exception e) {
e.printStackTrace();
}
}
}

実行した結果、sigma = 0.0となるので、コードが間違っていなければどうやらx,y,x1,y1については(Math.PI / 180)を先に計算することによる誤差の影響はないようです。(int)(r * Math....)となっている以上、rが十分大きくないと誤差の影響で結果が変わることはないということなんでしょうね。

#1さんのコメントにちょっと指摘を。
> drawLine()の処理がずっと重いので、Math.PI / 180の部分を別の定数に置き換えてもトータルの実行時間には殆ど影響しないでしょう。

自分のコードが間違っていなければ、目に見えた差があるように思います。

> それと定数同士の割り算が(複数回)出てきますが、多分コンパイル時に最適化処理の一環として割り算結果を別の乗数として出力したりすると思われますので、この点からもそのままでも問題無いと思われます。

演算子順序として、*,/は左結合で、結合順序によって結果が変わるものなので、最適化しても * Math.PI / 180と* (Math.PI / 180)は同じにはならないと思います。もしそういう最適化をしたらコンパイラのバグでしょう。
    • good
    • 0

数学の法則から


a * b / c
= a * b * ( 1 / c ) ※ 割り算は、逆数のかけ算と同じ
= a * (b * ( 1/c ) ) ※ かけ算だけのときは、計算順を変えても同じ(結合法則)
= a * (b/c) ※ かけ算は、逆数の割り算と同じ
ですよね?

double pi180= Math.PI / 180 ;
等と先に計算した結果を変数に残しておいて
Math.cos( i * pi180 )
等と使う、ということです。

割り算が1つ減ります。


計算誤差を考えると
・ i * Math.PI / 180 の方が誤差が少ないです。(i*Math.PIで発生した誤差が、/180することで小さくなるので)
・for ( double i = 0.0; i < 360.0; i += 360.0 / n )
これは、ループの度に、 360.0/n の誤差が累積するので、あまりよくありません。
for(int i0=0;i0<n;i0++)
等と、ループ回数でのループを作って
dounle i = 360.0 * (double)i0 / (double)n ;
等と、回数に対応した角度を計算すると、誤差が少なくなります。また、度数での角度を使用しないのなら、
i * Math.PI / 180
=(360.0 * (double)i0 / (double)n) * Math.PI / 180
= 2.0 * (double)i0 * Math.PI / (double)n
とすることもできます。



> for( double j = i + 360 / n; j < 360.0; j += 360.0 / n )

iの最小値は0.0ですから、i+360は360以上になります。
よって、 j<360.0は常に偽であり、このループは一度も実行されないということになります。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!