アプリ版:「スタンプのみでお礼する」機能のリリースについて

<要求事項>
分数は、 例)1|3 のように表す。
1.分母がゼロの時はエラーとする。
2.除算において、除数がゼロの入力エラーに対しては、再入力するように促す。
3.以下範囲の整数(分子、分母にかかわらず)に対して、正しく計算できるようにする。
  -2147483648 ~ 2147483647

4.計算結果については,分母が1の時には分子のみ表示。分数がの時には0のみを表示。最終計算結果は既約分数にする。分数が負数の場合、-を分数の前に表示。

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

int main(void)
{
int b,d; /* 分母*/
int a,c; /* 分子*/
int sign,sign2,sign3,sign4;
int yakusu,yakusu2,yakusu3,yakusu4; /* 最大公約数*/

printf("分母= ");
scanf("%d",&b);

printf("分子= ");
scanf("%d",&a);

if(b==0){
printf("分母が0です。入力が誤っています。\n"); /*分母が0ならエラーとする*/
return 0;
}
if (a==0){
printf("分数1= 0\n"); /*分子が0のとき*/
}
else{
printf("分数1=%d|%d\n",a,b); /*一つ目の分数*/
}
printf("\n");

printf("分母= ");
scanf("%d",&d);

printf("分子= ");
scanf("%d",&c);

if(d==0){
printf("分母が0です。入力が誤っています。\n"); /*分母が0ならエラーとする*/
return 0;
}
if (c==0){
printf("分数2= 0\n"); /*分子が0のとき*/
}
else{
printf("分数2=%d|%d\n",c,d); /*二つ目の分数*/
}
printf("\n");

/* 足し算:a|b + c|d = (a*d + b*c) | (b*d) */
printf("足し算:%d|%d + %d|%d\n",a,b,c,d);

yakusu = gcd(abs(a*d + b*c),abs(b*d)); /*最大公約数を求める*/
sign = (a*d + b*c)/abs(a*d + b*c) * (b*d/abs(b*d));

if(sign * abs(a*d + b*c)/yakusu == 0){ /*分子が0となる時*/
printf("0\n");
}
if (abs(b*d)/ yakusu!= 1){
printf("既約分数は %d|%d\n" ,sign * abs(a*d + b*c)/yakusu , abs(b*d)/yakusu );
}
else{
printf("既約分数は %d\n" ,sign * abs(a*d + b*c)/yakusu ); /*分母が1の場合*/
}

printf("\n");

/* 引き算:a|b - c|d = (a*d - b*c) | (b*d) */
printf("引き算:%d|%d - %d|%d\n",a,b,c,d);

yakusu2 = gcd(abs(a*d - b*c),abs(b*d)); /*最大公約数を求める*/
sign2 = (a*d - b*c)/abs(a*d - b*c) * (b*d/abs(b*d));

if(sign2 * abs(a*d - b*c)/yakusu2 == 0){ /*分子が0となる時*/
printf("0\n");
}
if (abs(b*d)/ yakusu2!= 1){
printf("既約分数は %d|%d\n" ,sign2 * abs(a*d - b*c)/yakusu2 , abs(b*d)/yakusu2 );
}
else{
printf("既約分数は %d\n" ,sign2 * abs(a*d - b*c)/yakusu2 );
}

printf("\n");

/* 掛け算:a|b * c|d = (a*c) | (b*d) */
printf("掛け算:%d|%d * %d|%d\n",a,b,c,d);

yakusu3 = gcd(abs(a*c),abs(b*d)); /*最大公約数を求める*/
sign3 = (a*c)/abs(a*c) * (b*d/abs(b*d));

if(sign3 * abs(a*c)/yakusu3 == 0){ /*分子が0となる時*/
printf("0\n");
}
if (abs(b*d)/ yakusu3!= 1){
printf("既約分数は %d|%d\n" ,sign3 * abs(a*c)/yakusu3 , abs(b*d)/yakusu3 );
}
else{
printf("既約分数は %d\n" ,sign3 * abs(a*c)/yakusu3 ); /*分母が1の場合*/
}

printf("\n");

/* 割り算:a|b / c|d = (a*d) | (b*c) */
printf("割り算:%d|%d / %d|%d\n",a,b,c,d);

yakusu4 = gcd(abs(a*d),abs(b*c)); /*最大公約数を求める*/
sign4 = (a*d)/abs(a*d) * (b*c/abs(b*c));

if(sign4 * abs(a*d)/yakusu4 == 0){ /*分子が0となる時*/
printf("0\n");
}
if (abs(b*c)/ yakusu4!= 1){
printf("既約分数は %d|%d\n" ,sign4 * abs(a*d)/yakusu4 , abs(b*c)/yakusu4 );
}
else{
printf("既約分数は %d\n" ,sign4 * abs(a*d)/yakusu4 );/*分母が1の場合*/
}
return 0;
}
int gcd(int x,int y){
/* ユークリッド互除法*/
int z;
if( (x <= 0) || (y <= 0) ){
return -1;
}
z = x % y;
while (z != 0){
x = y;
y = z;
z = x % y;
}
return y;
}

>>大学の課題です。現在の状態は上記の通りです。このプログラムだと、答えの既約分数が0になると表示できなかったり、桁が大きい数で計算しようとすると値がおかしくなってしまいます。
どなたかプログラム改良にご助力願えないでしょうか?

A 回答 (6件)

No.2です


>オーバーフローというものは良く分からないのですが、アドバイスを踏まえて色々やってみます。

オーバーフローの概念だけ簡単に。
例えば、0~99までが入る変数型「sample」があったとします(ここでわかりやすく10進数でいきます)

sample a, b, c;
a = 99;
b = 99;
c = 0;
c = a * b;

という計算を行った場合、cの値はいくつになるでしょうか?
単純に「a * b」を計算すると「99 * 99 = 9801」となります。
しかし、sample型で扱うるの「0~99」です。
そのため100以上の値は「c」に格納することができず桁あふれ(オーバーフロー)が発生してしまいます。
このため、桁あふれ分はcに格納することができず「c = a * b」の結果「c」には桁あふれで残った(sample型に格納できる)分「1」が入ることになります。

注:あくまで概念の説明です。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。オーバーフローの概念を理解することができ、課題もなんとか終わらせることができました。分かりやすい説明ありがとうございました。

お礼日時:2008/07/29 16:06

>桁が大きい数で計算しようとすると値がおかしくなってしまいます。


 
 ・今回の質問では、標準入力からの分母と分子は int なので、内部では、-2147483648 ~ 2147483647 と「正しく」扱われる。

 ・この分母、分子を「計算(かけ算、たし算)しよう」とした時、「おかしく」なる。

   例)2147483647 + 1 = -2147483648  -1 * -2147483648 = -2147483648

 ・「おかしく」なる理屈は、分母、分子、丸ごとスタックにおくられ、演算が行われる際に、有限のスタック(今回は32ビット)に【納めきれなくなる】。
 
☆そんなら、丸ごとスタックにやらず、1桁ずつ「計算」をすればよいのでは・・。

 例えば、筆算で、
 
  123
 ×234
 
 の計算をするとき、まず3×4をやって12の2を・・と下位から1桁ずつ確定していきますよね。
 この過程をコード化すればよいのではと・・。
 
 で重要なのは、この結果は、「表示」にしか用いない(というか用いられない)。

 今回の質問内容では、これで十分ですよね↑・・。

 巷のπの計算だって、何万桁まで「内部で扱う数」としてでなく、「結果表示」でのみ有効なものですよね。
 でないと、正しいか検証もできないし・・。
------------------------------------------------
★「足し算」、「かけ算」について、筆算もどきのソースを投稿します(BorlandC++5.6.4)。

 「値がおかしくなって」しまう数で呼び出してみて下さい。

・「足し算」は、オーバーフローするか判定し、するものについて筆算もどきしてます。
  判定:「演算」で、正+正が負のとき、負+負が正のときをオーバーフローとしています。
・「かけ算」は、上述の方法です。

共に、20個の配列(20桁)で「筆算もどき」しています。
-------------------------------------------
#include <stdio.h>
#include <stdlib.h>

void Output( char cKekka[] )
{
 int i, iSumi = 0;

 for( i = 0; i <= 20; i++ ){

  if( ( 0 == iSumi ) && ( 0x00 == cKekka[ i ] ) ) continue;

  printf( "%c", ( 0x30 + cKekka[ i ] ) );

  iSumi = 1;
 }
 printf( "\n" );
}
void Tashizan( int iVal1, int iVal2 )
{
 int iOver = 0, kk = 20, iDum;
 char cKekka[32] = { 0x00 };

 if( iVal1 <= 0 ){ // オーバーフロー判定

  if( iVal2 < 0 ){ // 双方負

   iOver = iVal1 + iVal2;

   if( iOver >= 0 ) iOver = 1;
  }
 }
 if( iVal1 > 0 ){ // オーバーフロー判定

  if( iVal2 > 0 ){ // 双方正

   iOver = iVal1 + iVal2;

   if( iOver <= 0 ) iOver = 2;
  }
 }
 if( ! iOver ){ // オーバーフローしない:通常「演算」

  printf( "%d\n", ( iVal1 + iVal2 ) );
 }
 if( iOver ){ // オーバーフローするので筆算もどき

  while( iVal1 || iVal2 ){ // 1桁ずつの足し算

   iDum = cKekka[ kk ];
   iDum += abs( iVal1 % 10 ); // 下1桁
   iDum += abs( iVal2 % 10 );

   cKekka[ kk ] = (char)( iDum % 10 );
   cKekka[ kk - 1 ] = (char)( iDum / 10 ); // 繰り上がり

   kk--; // 上位の桁へ

   iVal1 /= 10;
   iVal2 /= 10;
  }
  if( 1 == iOver ) printf( "-" );

  Output( cKekka );
 }
}
void Kakezan( int iVal1, int iVal2 )
{
 int i, j, kk, iTotal = 0;
 char c1[ 32 ] = { 0x00 }, c2[ 32 ] = { 0x00 };
 char cKekka[ 32 ] = { 0x00 };

 if( iVal1 < 0 ) c1[ 31 ] = 0x01; // 負
 if( iVal2 < 0 ) c2[ 31 ] = 0x01;

 for( i = 20; i >= 0; i-- ){ // 1桁ずつ配列へ

  c1[ i ] = (char)abs( iVal1 % 10 );
  c2[ i ] = (char)abs( iVal2 % 10 );

  iVal1 /= 10;
  iVal2 /= 10;
 }
 for( i = 20; i >= 0; i-- ){ // 下位から筆算もどき

  for( j = 20; j >= 0; j-- ){ // 被乗数桁

   kk = j - ( 20 - i ); // 格納桁

   if( ( kk - 1 ) <= 0 ) break;

   cKekka[ kk ] += (char)( c1[ j ] * c2[ i ] );

   cKekka[ kk - 1 ] += (char)( cKekka[ kk ] / 10 ); // 繰り上がり

   cKekka[ kk ] = (char)( cKekka[ kk ] % 10 );

   iTotal += cKekka[ kk ];
  }
 }
 cKekka[ 31 ] = (char)( c1[ 31 ] + c2[ 31 ] ); // 符号:奇数なら負

 if( iTotal && ( cKekka[ 31 ] % 2 ) ) printf( "-" );

 if( 0 == iTotal ) printf( "0" );

 Output( cKekka );
}
注:インデントに全角空白を用いています。タブに一括変換して下さい。
    • good
    • 1
この回答へのお礼

回答ありがとうございます。課題のほうですが、何とか終わらせることができましたので、お書き頂いたプログラムはこれからの学習に役立てていきたいとます。

お礼日時:2008/07/29 16:03

反則になるかもしれませんが、


1.64ビットの整数がつかえるなら、それを使用されてはいかがですか。
linuxならlong long int型になります。
これで、約20桁の数値が格納できます。
2.それでも足りないなら、多倍長演算のライブラリを使用します。
これで、メモリが許す限り、無限桁の数値が扱えます。
参考URLを参照下さい。(多倍長演算のライブラリの一例です)
3.また、C言語でなくてよいなら、rubyで計算することを推奨します。
rubyの変数は、上限及び下限がありません。四則演算程度なら、半日でrubyの文法は覚えられます。

参考URL:http://mitv2.net/software/tiny_mp/tiny_mp.html
    • good
    • 0
この回答へのお礼

アドバイスありがとうございます。
long int~の話は初耳だったので試してみます。URLの方も参考にさせていただきます。

お礼日時:2008/07/24 20:18

「桁が大きいと結果がおかしい」というのは, 多分オーバーフローですね. ことあるごとに gcd を使って約分していくしかないと思います.


例えば a/b + c/d を計算するのに, (ad + bc) / (bd) としてしまってはダメで, 分母を最初から b と d の最小公倍数にする必要があります. とはいっても, これだけで本当に十分とは思えないんですが....
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
正直なところ、gcd関数はあまり理解できていないのですが、もう少しがんばって考えてみます。

お礼日時:2008/07/24 20:05

>桁が大きい数で計算しようとすると値がおかしくなってしまいます


の部分ならある程度推測できます。
「int * int」の式が多いですが、この乗算の結果がintの最大値をオーバーフローしている場合に問題が出ていませんか?
オーバーフローしないように工夫する必要はあります。
例えば
sign3 = (a*c)/abs(a*c) * (b*d/abs(b*d));
 ↓
sign3 = (a/abs(a) * c/abs(c)) * ((b/abs(b) * d/abs(d));
とか(元の計算式があってるかは確認していません)
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
オーバーフローというものは良く分からないのですが、アドバイスを踏まえて色々やってみます。

お礼日時:2008/07/24 20:00

気になる点


・ ユークリッド互除法gcdの入力の制約を満たしていない
 xがy以上.x,yは自然数の制約.
 引数の0であるのにgcdを呼ぶ場合があるのでは?
・符号のチェックが少しわかりにくい
 sign = (a*d + b*c) / (b*d);
 sign = sign / abs(sign);
 こうしたほうがあとから見てわかりやすいのでは?
・冗長な部分が多い
 分数の入力,計算結果の表示は関数にできますね.
    • good
    • 0
この回答へのお礼

ご指摘ありがとうございます。
アドバイスを踏まえ、もう一度再考した結果、より短くまとめることができました。

お礼日時:2008/07/24 19:58

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