
下記のプログラムを作ったのですが、
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で質問しましょう!
似たような質問が見つかりました
- Java java 入力 3 4 3 出力 ABC DEFG HIJ このようなプログラムの書き方を教えてくだ 2 2022/07/15 14:18
- Ruby 【JAVA】数字をひし形に出力するプログラムについて 2 2022/07/11 23:32
- Java Java プログラム public class Main { public static void 3 2023/08/10 23:46
- Java Java 配列<選挙> 4 2023/07/31 15:07
- その他(プログラミング・Web制作) pythonのグローバル変数 2 2022/11/25 18:02
- Java javaでのプログラム(配列)について質問です. 2 2022/10/14 22:27
- その他(プログラミング・Web制作) Pythonによる物理の斜方投射の位置座標表示について 2 2023/06/05 12:46
- Java 直し方について教えて頂きたいです。 4 2022/08/13 02:11
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- Java Java です 途中は省いてますが for(int i = 0; i < 25; i ++) { s 4 2022/05/20 23:36
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
double型変数値の整数部分のみ...
-
実数からの小数部の取得
-
プログラミング演習エラーについて
-
挿入演算子<<をオーバーロード...
-
Vectorクラスの使い方
-
javaアプレット repaintのタイ...
-
ピクチャボックスのクリックイ...
-
JAVAサーブレット。 HTML...
-
わかりません
-
System.err. printlnとSystem.o...
-
要素数が10の配列で、乱数0~9...
-
Javaで改行などが出来ないのです。
-
Excelの配列数式について
-
C言語のポインターに関する警告
-
変数を動的に利用するには?
-
String型の値が大文字か小文字...
-
JPanelの重ね方/OverlayLayout他
-
javaの演算子の部分ですが 4行...
-
C# 特定文字の数を調べる方法を...
-
for文を使った累乗の計算方法に...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
実数からの小数部の取得
-
0dの意味を教えてください
-
べき乗
-
double型変数値の整数部分のみ...
-
フラクタルで雲を作る。
-
Javaで何パーセント%かを表示...
-
java のfor文について質問です。
-
最大値と最小値の求め方
-
プログラムのおかしいところを...
-
double型の足し算について
-
演算子を使わない演算
-
Javaによる利率計算の実装方法
-
四捨五入をするメソッドを実装...
-
Javaで計算
-
実数 2.0 に対して int(2.0) ==...
-
99.98+0.01の誤差
-
Java math.powメソッド
-
doubleで入力するには?
-
Javaのeval関数処理
-
掛け算演算を使わない掛け算
おすすめ情報