LoginSignup
0
0

More than 1 year has passed since last update.

【C言語】四捨五入,atoi(),精度が倍の10進整数

Last updated at Posted at 2022-08-10

以前の質問でも取り上げた、標準入力された6つの整数値の平均値を算出し、四捨五入して小数第1位まで求めて結果を出力するようなプログラムについてです。

エラーになったプログラム
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	
	char buf[100];
	int a,b,c,d,e,f;
	
	fgets(buf,sizeof(buf),stdin);
	sscanf(buf,"%d %d %d %d %d %d",&a,&b,&c,&d,&e,&f);
	
	double ave = (a+b+c+d+e+f) / 6;
	int int_ave = (a+b+c+d+e+f) / 6;
	
	char str[100];
	snprintf(str,sizeof(str),"%1f",ave);   //aveを文字列に変換する
	
	char* p = strchr(str,'.');   //小数点の位置str[X]をつき止める
	if(p != NULL){
		if(str[(p-str)+2] >= 48 && str[(p-str)+2] <= 52){   //str[X] (小数点) の2個隣 (小数第2位) の文字が「0」~「4」だった場合
			int m = atoi(&str[(p-str)+1]);   //str[X] (小数点) の1個隣 (小数第1位) の文字を数値に変換して
			printf("%0.1f\n",int_ave+0.1*m);   //計算式:平均値の整数版+小数第1位の文字 (int) ×0.1 (1/10の位だから)
	    }else if(str[(p-str)+2] >= 53 && str[(p-str)+2] <= 57){   //str[X] (小数点) の2個隣 (小数第2位) の文字が「5」~「9」だった場合
			int n = atoi(&str[(p-str)+1]);   //str[X] (小数点) の1個隣 (小数第1位) の文字を数値に変換して
			printf("%0.1f\n",int_ave+0.1*(n+1));   //計算式:平均値の整数版+(小数第1位の文字 (int) に1を加えた (繰り上げた) 数字)×0.1 (1/10の位だから)
		}
	}
	
}

分からないと嘆く以前の ”しょうもない” ミス以外は修正せず、なるべく当時のままにしています。

・どうやらatoi()の引数はchar型なら何でも良いわけではなく、文字列限定らしい
出力フォーマットに%.1fを使うと一発で出てくる

atoi()の部分で躓いていた時、フォーマット"%c"のchar型文字をそのまま引数に使おうとして、その度にエラーを受けていました。また、atoi()について色々とググってみても 引数に文字型変数を用いている例が見つからなかったので、コチラのサイトで述べられている通り、あまり深掘りせずに解釈して、atoi()は文字列を整数型の数値に変換する関数なんだと納得するに至りました。また、そもそもの話、出力フォーマット指定子についての知識があれば数行で書けてしまう爽やかなプログラムだった事も分かりましたprintf()って意外に万能なんだな、と素直に思いました。
ただ、せっかく時間をかけた部分なので、遠回りと分かった方法も興味がてら眺めてみました。strの要素を文字列としてatoi()に渡すためにどうすれば良いかを考え、それなら専用の変換ツールを作ろうという事になって、追加で「要素数2の文字列を用意し、前に引数の文字、後に\0を格納する」関数を作りました。個人的に、文字を文字列として使えるようにするには手っ取り早い手段だと思っています。

char *chr_s(char buffer[2], char c){
	buffer[0] = c;
	buffer[1] = '\0';
	return buffer;
}

作成した関数を早速プログラムに入れて、atoi()に対応させてみました。結果、ちゃんとchr_sを引数として受け入れてくれて、さらには無事コンパイル・実行出来たので全体的にも良かったのではないかと思います。(⇨訂正版あり)

正常に動いているようで 実は正しくない?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char buffer[2];
char *chr_s(char c){
	buffer[0] = c;
	buffer[1] = '\0';
	return buffer;
}

int main(){
	
	char buf[100];
	int a,b,c,d,e,f;
	
	fgets(buf,sizeof(buf),stdin);
	sscanf(buf,"%d %d %d %d %d %d",&a,&b,&c,&d,&e,&f);
	
	double ave = (a+b+c+d+e+f) / 6.0;
	int int_ave = (a+b+c+d+e+f) / 6;
	printf("%1f\n",ave);   //xxxxxxxx   動作確認用
	printf("%.1f\n",ave);   //xxxxxxxx   動作確認用:本来ならここで答えを出せる!
	
////////////////////////////////////////////////
	
	char str[100];
	snprintf(str,sizeof(str),"%1f",ave);   //aveを文字列に変換する
	printf("%s\n",str);   //xxxxxxxx   動作確認用
	
	char* p = strchr(str,'.');   //小数点の位置str[X]をつき止める
	if(p != NULL){
		printf("小数点の位置はstr[%1ld]です\n",p-str);   //xxxxxxxx   動作確認用
		int m = atoi(chr_s(str[(p-str)+1]));//str[X] (小数点) の1個隣 (少数第1位) の文字を数値に変換して
		if(str[(p-str)+2] >= 48 && str[(p-str)+2] <= 52){   //str[X] (小数点) の2個隣 (少数第2位) の文字が「0」~「4」だった場合
			printf("小数第1位の数字は%dです\n",m);   //xxxxxxxx   動作確認用
			printf("四捨五入した値:%0.1f\n",int_ave+0.1*m);   //計算式:平均値の整数版+少数第1位の文字 (int) ×0.1 (1/10の位だから)
		}else if(str[(p-str)+2] >= 53 && str[(p-str)+2] <= 57){   //str[X] (小数点) の2個隣 (少数第2位) の文字が「5」~「9」だった場合
			printf("小数第1位の数字は%dです\n",m);   //xxxxxxxx   動作確認用
			printf("四捨五入した値:%0.1f\n",int_ave+0.1*(m+1));   //計算式:平均値の整数版+(少数第1位の文字 (int) に1を加えた (繰り上げた) 数字)×0.1 (1/10の位だから)
		}
	}
	
}

ちなみに、プログラムの作成中、printf("小数点の位置はstr[%◯]です\n",p-str);の出力フォーマット指定子の部分で何度もエラーに引っかかりました。"%d""%1d""%1ld"と (後ほど、いずれも正しくなかったと判明)、エラーメッセージに従い2度ほど変更を行いました。このうち "%1ld" というのが謎だったので、これを別途質問しました。

ここで、「1」の仕組みを把握。桁数に関係しているようです。
1⃣

正常に動いているようで 実は正しくない?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	char str[] = "abc@fghijk";
	char* p = strchr(str, '@');
	if(p != NULL){
		printf("[%5ld]\n", p-str);
	}
}

⇩1⃣の出力結果

[    3]

2⃣

正常に動いているようで 実は正しくない?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	char str[] = "abc@fghijk";
	char* p = strchr(str, '@');
	if(p != NULL){
		printf("[%-10ld]\n", p-str);
	}
}

⇩2⃣の出力結果

[3         ]

参考:

僕が今回覚えておかなければならない指定子は「整数を10進数で入力するための変換指定子であり、変数の型はlong int"%ld"」でした。ただし、上のプログラムで使われているポインタ同士の減算「p-str」の変数型は"%ld"でも"%1ld"でもなく、"%td"である事が分かりました。

正常に動いているようで 実は正しくない?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	char str[] = "abc@fghijk";
	char* p = strchr(str, '@');
	if(p != NULL){
		long int value = p-str;
		printf("[%ld]\n", value);
	}
}

*****
今回は以上です。修正すべき点等あれば、ご指摘お願いします。

P.S.

あれ?この書き方なら "%d" でも動いた。

正常に動いているようで 実は正しくない?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	char str[] = "abc@fghijk";
	char* p = strchr(str, '@');
	if(p != NULL){
		int value = p-str;
		printf("[%d]\n", value);
	}
}

≪訂正≫
皆さんからのアドバイスをもとに、プログラムを作り直してみました。コメント頂き、ありがとうございます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
	
	char buf[100], str[100];
	int a, b, c, d, e, f;
	
	fgets(buf, sizeof(buf), stdin);
	sscanf(buf, "%d %d %d %d %d %d", &a, &b, &c, &d, &e, &f);
	
	double ave = (a+b+c+d+e+f) / 6.0;
	int int_ave = (a+b+c+d+e+f) / 6;
	printf("%1f\n", ave);   //xxxxxxxx   動作確認用
	
////////////////////////////////////////////////
	
	char* str_p = str;
	
	snprintf(str, sizeof(str), "%1f", ave);   //aveを文字列に変換する
	printf("%s\n", str);   //xxxxxxxx   動作確認用
	
	char* p = strchr(str, '.');   //小数点の位置str[X]をつき止める
	if(p != NULL){
		printf("小数点の位置はstr[%td]です\n", p-str_p);   //xxxxxxxx   動作確認用
		int m = str[(p-str)+1] - '0';   //str[X] (小数点) の1個隣 (少数第1位) の文字を数値に変換して
		if(str[(p-str_p)+2] >= 48 && str[(p-str_p)+2] <= 52){   //str[X] (小数点) の2個隣 (少数第2位) の文字が「0」~「4」だった場合
			printf("小数第1位の数字は%dです\n", m);   //xxxxxxxx   動作確認用
			printf("四捨五入した値:%0.1f\n", int_ave+0.1*m);   //計算式:平均値の整数版+少数第1位の文字 (int) ×0.1 (1/10の位だから)
		}else if(str[(p-str_p)+2] >= 53 && str[(p-str_p)+2] <= 57){   //str[X] (小数点) の2個隣 (少数第2位) の文字が「5」~「9」だった場合
			printf("小数第1位の数字は%dです\n", m);   //xxxxxxxx   動作確認用
			printf("四捨五入した値:%0.1f\n", int_ave+0.1*(m+1));   //計算式:平均値の整数版+(少数第1位の文字 (int) に1を加えた (繰り上げた) 数字)×0.1 (1/10の位だから)
		}
	}
	
}

(2022/08/11)

0
0
12

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0