
下記のプログラムを作ったのですが、
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(); }
}
}
No.1ベストアンサー
- 回答日時:
確かに (j * Math.PI / 180)の中の*,/演算のどちらを先に実行するかで結果の最下位ビット等が異なる場合もあるかも知れません。
しかしそれは他にもプログラム内に出てくる短精度/倍精度の演算全てについて言える事で、特定の計算ステップを抜き出して問題にしても始まらないのではと思われます。
(全体として短(倍)精度で十分だとの判断がある筈なので)
drawLine()の処理がずっと重いので、Math.PI / 180の部分を別の定数に置き換えてもトータルの実行時間には殆ど影響しないでしょう。
それと定数同士の割り算が(複数回)出てきますが、多分コンパイル時に最適化処理の一環として割り算結果を別の乗数として出力したりすると思われますので、この点からもそのままでも問題無いと思われます。
(結果的には同じ筈ですが、Math.PI / 180.0 と書く方が良いのではと思われます)
No.3
- 回答日時:
結論から言うと、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)は同じにはならないと思います。もしそういう最適化をしたらコンパイラのバグでしょう。
No.2
- 回答日時:
数学の法則から
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は常に偽であり、このループは一度も実行されないということになります。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
実数からの小数部の取得
-
javaでC++のdefine文に相当する...
-
99.98+0.01の誤差
-
0dの意味を教えてください
-
Javaによる利率計算の実装方法
-
C言語のポインターに関する警告
-
System.err. printlnとSystem.o...
-
JavaScriptの変数をjavaのメソ...
-
ORA-01858: 数値を指定する箇所...
-
VBAで配列の計算
-
javaで質問です。 文字列2023/2...
-
JScrollPaneで、表示がおかしく...
-
JavaScriptを使ってロト6の当...
-
streamで送信されたArrayListを...
-
JavaScriptとVBScriptに関して
-
IF関数でEmpty値を設定する方法。
-
動的配列が存在(要素が有る)か...
-
ループ処理の際、最後だけ","を...
-
オブジェクトの中のプロパティ...
-
1~100までの数字を表示したい
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
0dの意味を教えてください
-
double型変数値の整数部分のみ...
-
Javaで何パーセント%かを表示...
-
実数からの小数部の取得
-
べき乗
-
乱数のdouble型について
-
Java 7日後までの天気を予測する
-
自然対数の底Eを含むStringの数...
-
printfでのエラーがわからない...
-
double型の足し算について
-
最大値を求めるプログラム
-
最大値と最小値の求め方
-
ダイアログベースの3次Spline...
-
分を表す数値(int型)を、小数...
-
プログラムのおかしいところを...
-
演算子を使わない演算
-
doubleについて
-
Javaプログラムのフローチャー...
-
Javaコンパイルエラー
-
プログラマーの達人という本の...
おすすめ情報