_tstofの代わりの関数

ソースコードを公開してみんなにつっこんでもらおう!
という主旨のグループです。
返信
記事: 26
登録日時: 9年前
住所: 新潟

_tstofの代わりの関数

#1

投稿記事 by » 8年前

 windows ce上で_tstof()を使おうとしたら、「そんな関数定義されてない」とコンパイラに怒られたため、_tstof()関数の換わりの関数を作ってみました。
変数の命名法がよくわからないため、なおのこと読みづらいコードになっていると思いますが、その点も含めて酷評希望です。

コード:

#include <stdio.h>
#include <TCHAR.h>
#include <math.h>
double my_tstof(TCHAR buf[]);

int main(void){
	double x;
	if((x=my_tstof(_T("-1.45"))) == NULL)
		printf("Error\n");
	return 0;
}

double my_tstof(TCHAR buf[]){	//double型の変域が広すぎるが利用しないため符号含めず小数点含め16桁の範囲で行う(double型の変域+-10^-307~ +-10~308)
	double ans=0;
	TCHAR hugo;
	TCHAR x[16];
	TCHAR numbuf[2];
	int Pcount=0;	//小数点の位置、x[0]を位置0としたときの数値を代入する(小数点を除く前の時点で) 小数点を除いた文字数-Pcountの10乗でansを最終的に除算
	int k;
	
	//符号を取り除く
	if(buf[0] == '+' || buf[0] == '-'){
		hugo = buf[0];
		for(int k=0;k<(int)_tcslen(buf);k++){
			x[k] = buf[k+1];
		}
	}
	//小数点の個数が1個以下であるかを確かめる	また、小数点の位置を記録する
	k=0;
	if(x[0] == '.')	//エラー、数値として正しくない
		return NULL;
	for(int i=0;i<(int)_tcslen(x);i++){	//kは小数点の数をこのループ内では表す
		if(x[i] == '.'){
			k++;
			Pcount=i;
		}
		if(k >= 2)
			return NULL;
	}
	//小数点を取り除く
	k=0;
	for(int i=0;i<(int)_tcslen(x);i++){
		if(x[i] != '.'){
			x[k++] = x[i];
		}
	}
	x[k++]='\0';
	//ansに自然数としてx[]から格納
	numbuf[1]='\0';
	k=(int)_tcslen(x);
	for(int i=0;i<(int)_tcslen(x);i++){
		numbuf[0]=x[i];
		k--;
		ans = ans+((int)_ttoi(numbuf)*(int)pow((double)10,(double)k));
	}
	//符号と小数点の処理
	ans = ans / (pow((double)10,(double)(_tcslen(x)-Pcount)));
	if(hugo == '-')
		ans = ans *(-1);

	return ans;
}

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: _tstofの代わりの関数

#2

投稿記事 by ISLe » 8年前

レビューでなくてすみません。
未確認ですが、以下のいずれかの方法が使えるようですが。
  • WideCharToMultiByteでANSI文字列(char)に変換してatof
  • _tcstod

アバター
a5ua
記事: 199
登録日時: 9年前

Re: _tstofの代わりの関数

#3

投稿記事 by a5ua » 8年前

いくつかの気づいた点を、重要度の高そうな順に並べてみます
  • 16桁を超える入力があると、範囲外アクセスが生じる
  • my_tstofの引数は、const TCHAR buf[]とするべき
  • "0.2"など、先頭に符号がない場合、エラーになる
  • "-5"などの整数表現に対して、正しい結果が得られない
  • " -2.5"など、先頭に空白があるとエラーになる
  • ".1"や"5e-2"などへの対応(これはなくてもいい気がする)

記事: 26
登録日時: 9年前
住所: 新潟

Re: _tstofの代わりの関数

#4

投稿記事 by » 8年前

・WideCharToMultiByteでANSI文字列(char)に変換してatof
・_tcstod
どちらの関数も調べてみました。コンパイラから「定義されていない」と起こられることもなく、まだ、試していないのですが、調べた限りではうまくいきそうです。一回charに変換すればいいというのは盲点でした。_tstofが使えないならatofもつかえないだろうという先入観に取り付かれていました。

>>16桁を超える入力があると、範囲外アクセスが生じる
  windows ce上で自分がプログラミングする上では、16桁を超える変数を扱うとしたら、その数値自体がもともとエラーになるべきものなので、16桁までにしました。やはり、汎用性を考えてもっと広い範囲を使えるようにメモリの動的確保等で対応するべきなのでしょうか?

>>"0.2"など、先頭に符号がない場合、エラーになる
>>"-5"などの整数表現に対して、正しい結果が得られない
  そのとおりですね。もっとしっかりとテストをするべきでした。

">> -2.5"など、先頭に空白があるとエラーになる
  my_tstof()内部で処理したほうがいいのでしょうか?引数に渡す値にエラーがないかチェックしてから渡したほうがバグを減らせるように感じるのですが。。。

>>".1"や"5e-2"などへの対応(これはなくてもいい気がする)
  ".1"とはいったいどんな数でしょうか?自分の知識不足のため、なにを表しているのかわかりません、すみませんが、教えていただけないでしょうか? eとは自然対数のことでいいのですよね?対数や複素数はこのmy_tstof()を呼び出すプログラム中で使わないことがはっきりとしているので、わざと作っていません。(三角関数等も同じ理由から実装していません)

修正版のコードを載せます

コード:

#include <stdio.h>
#include <TCHAR.h>
#include <math.h>

double my_tstof(const TCHAR buf[]);


int main(void){
	printf("%f\n",my_tstof(_T("123")));
	printf("%f\n",my_tstof(_T("12.3")));
	printf("%f\n",my_tstof(_T("-123")));
	printf("%f\n",my_tstof(_T("-12.3")));

	return 0;
}

double my_tstof(const TCHAR buf[]){	//double型の変域が広すぎるが利用しないため符号含めず小数点含め16桁の範囲で行う(double型の変域+-10^-307~ +-10~308)
	double ans=0;
	TCHAR hugo='+';
	TCHAR x[16];
	TCHAR numbuf[2];
	int Pcount=0;	//小数点の位置、x[0]を位置0としたときの数値を代入する(小数点を除く前の時点で) 小数点を除いた文字数-Pcountの10乗でansを最終的に除算
	int k;
	
	//符号を取り除く
	if(buf[0] == '+' || buf[0] == '-'){
		hugo = buf[0];
		for(int k=0;k<=(int)_tcslen(buf);k++){
			x[k] = buf[k+1];
		}
	}else{
		for(int k=0;k<=(int)_tcslen(buf);k++){
			x[k] = buf[k];
		}
	}
	//小数点の個数が1個以下であるかを確かめる	また、小数点の位置を記録する
	k=0;
	if(x[0] == '.')	//エラー、数値として正しくない
		return NULL;
	for(int i=0;i<(int)_tcslen(x);i++){	//kは小数点の数をこのループ内では表す
		if(x[i] == '.'){
			k++;
			Pcount=i;
		}
		if(k >= 2)
			return NULL;
	}
	//小数点を取り除く
	k=0;
	for(int i=0;i<(int)_tcslen(x);i++){
		if(x[i] != '.'){
			x[k++] = x[i];
		}
	}
	x[k++]='\0';
	//ansに自然数としてx[]から格納
	numbuf[1]='\0';
	k=(int)_tcslen(x);
	for(int i=0;i<(int)_tcslen(x);i++){
		numbuf[0]=x[i];
		k--;
		ans = ans+((int)_ttoi(numbuf)*(int)pow((double)10,(double)k));
	}
	//符号と小数点の処理
	if(Pcount != 0)
		ans = ans / (pow((double)10,(double)(_tcslen(x)-Pcount)));
	if(hugo == '-')
		ans = ans *(-1);

	return ans;
}

アバター
a5ua
記事: 199
登録日時: 9年前

Re: _tstofの代わりの関数

#5

投稿記事 by a5ua » 8年前

16桁までしか扱えないのが問題ではなく、配列の範囲外にアクセスしてしまうのが問題だという指摘でした。
以下のプログラムで、_tcslen(buf)が17だとすると、
x[16]にアクセスしてしまうので、別のメモリ領域の値を破壊してしまう可能性があります。

コード:

    //符号を取り除く
    if(buf[0] == '+' || buf[0] == '-'){
        hugo = buf[0];
        for(int k=0;k<(int)_tcslen(buf);k++){
            x[k] = buf[k+1];
        }
    }
設計の好みの問題ではありますが、
my_tstofの仕様として、
「文字列をdoubleに変換する」
ただし、
(1) 変換できない文字列は絶対に入力されない
(2) 変換できない文字列が入力されたら0を返す
と、2通りの設計があると思います。

(1)の場合は、my_tstof_checkのような処理を別に用意することになるかと思います。

白さんのコードを見ると、(2)であると解釈したので、16桁を超える入力があった場合は、
0を返すべきだと思いました。


.1は0.1と同じ意味になります。
eを使った表現は、指数表現です。
5e-2は5×10-2 = 0.05を意味します。
double x = .1;
double y = 5e-2;
などは、C言語やC++では文法上正しい表記です。

記事: 26
登録日時: 9年前
住所: 新潟

Re: _tstofの代わりの関数

#6

投稿記事 by » 8年前

(int)_tcslen(buf)>=16のときにx[20]とかbuf[21]とかにアクセスして別のメモリ領域を書き換えてしまう、という理解で大丈夫ですか?今の今までそんなこと考えても見ませんでした。早速16桁を超える場合の処理を書きます。

>>double x = .1;
>>double y = 5e-2;
こんな表記方法もC/C++でも許さているのですか!!すごく勉強になります。今回はこのような入力を受け取ることがないのがわかっているので、使いませんが、今度必要になってくるはずなのでこのページをしっかりとブックマークします。

 罵詈雑言の嵐を予想していただけに、これほど親切に批評していただけ、ありがとうございました。

返信

“みんなでソースコードをレビューしよう” へ戻る