LoginSignup
wgatwt00a
@wgatwt00a (Rituryo kokka)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

C言語 小数点付き2進数→10進数に変換する方法

Q&AClosed

解決したいこと

お世話になります。
問題の内容は、”2 進表現の小数を 10 進表現の小数に変換する課題である.通常はこのように 2 進表現の小数が文字列で与えられることはない.2 進小数の理解を深めるための課題である.0 以上 1 未満の実数が 2 進表現でコマンドライン引数に与えられる.この実数を 10 進表現で小数第 6 位まで出力せよ.”です。
自分でプログラムを作成しましたがテストが途中で失敗し、考えてもわからないため皆様にお聞きしたいです。

発生している問題・エラー

==== テスト 7 (Test case 7) ====
$ ./binary2double 0.11110000
0.933594==== 期待される出力 (Correct output) ====
0.937500
==== 出力の違い (Different lines) ====
--- 7.correct   2023-02-01 09:00:05.819287500 +0900
+++ 7.out       2023-02-01 14:51:52.510809600 +0900
@@ -1 +1 @@
-0.937500
+0.933594
\ No newline at end of file
================
テスト 7 失敗(failed)

該当するソースコード

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
   double d, sum, x;
   d = atof(argv[1]);
   for (int i = 0; i < 8;i++){
     x = 1;
     d = d * 10;
     if (d >= 1) {
       for(int l = 0; l < i + 1 ;l++){
         x *= 0.5;
       }
       sum += x;
       d = d - 1;
     }
   } 
   printf("%.6f",sum);
   return 0;
}
0

7Answer

たとえば十進数の 0.1 という値を二進数で表現しようとしたとき、 0.0001100110011001100110011001100110011001100110011001101 というように同じパターンが無限に繰り返される循環小数となります。 十進数ではキリのよい値に見えても二進数の世界ではそうではありません。 double 型では正確に保持することが不可能なので double 型で格納しようとした段階で既に誤差が生まれています。

質問の事例では、数式上なら 1 ピッタリになるはずのときに実際には誤差で 0.999999999999988 となっているので d >= 1 が偽となり想定通り動いていませんでした。

小数点数を等号で比較するのはほぼ常に誤りだと言っても過言ではありません。 私も文字として処理するのが妥当だと思いますが、どうしても double の計算としてやりたいということであればあり得る誤差の幅の分だけ余裕をもって判定するという方法をとれなくはないです。 しかしどれだけの誤差があり得るのか、余裕をどう設定すればよいのか見積もる能力がない内にその方法をとるべきではないと私は考えます。

1

Comments

  1. @wgatwt00a

    Questioner
    なぜ失敗したのか初心者の私にもわかりやすく説明していただきありがとうございます。おかげで理解でき、すっきりしました!
    Vercleneさんにも言われたように他の方法で作成することにします。
    ありがとうございましたm(__)m
編集済 @imagou

0.933594と出力されるので

どうみても0.9375がexpectedなのに変な回答つけないでください.
手計算で簡単に検証できますので.

-7.correct   2023-02-01 09:00:05.819287500 +0900
+7.out       2023-02-01 14:51:52.510809600 +0900
@@ -1 +1 @@
-0.937500
+0.933594
1

自己レス:

あとは僅かな修正で0.11110000に対し0.937500を返すプログラにできると思います。

レスポンスもないんで修正書いときますね。

  #include <stdlib.h>
+ #include <math.h>
  int main(int argc, char *argv[]){
     double d, sum = 0.0, x;
+    const double S = 1e8;
     if (argc < 2) return 0;
-    d = atof(argv[1]);
+    d = round(S * atof(argv[1]));
     for (int i = 0; i < 8;i++){
       x = 1;
       d = d * 10;
-      if (d >= 1) {
+      if (d >= S) {
         for(int l = 0; l < i + 1 ;l++){
           x *= 0.5;
         }
         sum += x;
-        d = d - 1;
+        d = d - S;
       }

コマンド引数の2進数に桁数の制限がない版

#include <stdio.h>

int main(int argc, char *argv[])
{
    double sum = 0.0, x = 1.0;
    if (argc < 2) return 0;
    const char *p = argv[1];
    while (*p != '\0') {
        switch (*p++) {
        case '1':
            sum += x;
            /* fallthru */
        case '0':
            x /= 2;
            break;
        }
    }
    printf("%.6f\n",sum);
}

0

とりあえずatof関数は10進表現を入力にとるので,2進表現の入力に使っても意味がありません.
題意からして1文字ずつ自前でパースするのが良さそうですので,書き直しをオススメします.

それとC#はCとは全く異なる言語ですので,適切なタグを設定してください.

0

Comments

  1. @wgatwt00a

    Questioner
    そうなのですか。。できればこの方法でやりたいのでもう少し考えてみて無理そうなら他の方法でやってみようと思います。
    タグ直しました。ありがとうございますm(__)m
  2. そもそもatofを使うのが間違いと指摘しているのにこの方法でというのはどうにも.

@Verclene すみません、大きく勘違いしていたようです。失礼いたしました。
私の回答は削除いたしました。

0

@wgatwt00a さん、

自分でプログラムを作成しましたがテストが途中で失敗し、考えてもわからないため皆様にお聞きしたいです。

記事のコードをgcc 12.1.0を使用してコンパイルオプションに-Wall -Wextra -O2を指定してコンパイルすると

prog.c: In function 'main':
prog.c:3:14: warning: unused parameter 'argc' [-Wunused-parameter]
    3 | int main(int argc, char *argv[]){
      |          ~~~~^~~~
prog.c:13:12: warning: 'sum' may be used uninitialized [-Wmaybe-uninitialized]
   13 |        sum += x;
      |        ~~~~^~~~
prog.c:4:14: note: 'sum' was declared here
    4 |    double d, sum, x;
      |              ^~~

複数の警告が報告されますね。まずはこれを対策すべきでしょう。

  int main(int argc, char *argv[]){
-    double d, sum, x;
+    double d, sum = 0.0, x;
+    if (argc < 2) return 0;
     d = atof(argv[1]);

以上の修正で

  • argcが使用されていない
  • sumが未初期化

の警告は出なくなりました。
https://wandbox.org/permlink/g6yG2WmQnpi9NY3y

あと

発生している問題・エラー

のところで

0.933594==== 期待される出力 (Correct output) ====

プログラムの出力結果と「==== 期待される出力 (Correct output) ====」が繋がってしまってますね。

@@ -1 +1 @@
-0.937500
+0.933594
\ No newline at end of file

を見ても改行がないと言われています。これも修正すべきでしょう。

     } 
-    printf("%.6f",sum);
+    printf("%.6f\n",sum);
     return 0;

あとは僅かな修正で0.11110000に対し0.937500を返すプログラにできると思います。

0

@wgatwt00a さん、

ひとつ疑問なのですが、

0 以上 1 未満の実数が 2 進表現でコマンドライン引数に与えられる.この実数を 10 進表現で小数第 6 位まで出力せよ.

10進数で小数第6位まで出力すると0より大きい最小値は0.000001になるかと思いますが、

$$\log_2{0.000001}\fallingdotseq-19.9315685693$$

くらいなので出力精度が小数第6位までとなると引数の 2進数は小数点以下 20桁ほど指定する必要があると思います。
逆に言うと、2進数で0.00000000000000000001は 10進数で0.00000095367431640625でありこれを小数第6位までの出力とすると0.0000000.000001になると思います。

この質問にあるコードでは 入力値を 8桁分しか見ておらず指定されてる引数も0.11110000なので小数点以下 8桁しかありませんがこの点不足はないでしょうか?

0

Your answer might help someone💌