83462


C言語で以下のソースを実行したらtest1に0.80000000000000004と末尾に期待しない4が入っていて困っています。

[code]

    int main()
    {
        double test1 = 80 * 0.01;
        return 0;
    }
    
[/code]

環境は、Windows10、Visual Studio 2019です。

floatやdouble小数点以下の値には誤差が含まれる可能性があり、それによって誤差が含まれる可能性があることは分かりましたが、その誤差をどうやってなくすことができるかがわかりませんでした。

解決のためのアドバイスを頂けましたら、よろしくお願いします。


CC BY-SA 4.0
2021-11-06T06:28:53.120
83451
2021-11-08T13:05:35.067
21034
question
c
C言語でint型とdouble型のかけ算の結果がおかしい


double型では誤差をなくすことはできません。  
10進数の計算の場合では1を3で割ると0.333333333....となりそれに3をかけても0.999999....となりますので誤差が出ることがわかりやすいのですが、コンピュータの内部では2進数に変換され計算されますので、10進数ではあり得ない誤差が発生します。  
これは精度を上げることはできても、完全になくすことはできません。  
10進数での誤差が許容できるのであれば10進演算ライブラリを使えばいいと思います。  
下記にいろいろな演算ライブラリの説明がありますので参考にしてみてください。  
<https://ja.wikipedia.org/wiki/%E4%BB%BB%E6%84%8F%E7%B2%BE%E5%BA%A6%E6%BC%94%E7%AE%97#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA>


CC BY-SA 4.0
2021-11-06T07:31:23.540
83452
2021-11-06T07:31:23.540
24490
83451
answer


予め元の値の (小数点以下の) 桁数が分かっているなら、それに合わせて結果も `printf` 等で桁数を指定して参照するのが簡単な気がします。

[code]

    printf("%.2f", test1);
    
[/code]


CC BY-SA 4.0
2021-11-06T08:19:40.800
83453
2021-11-06T08:19:40.800
3060
83451
answer


他の方の回答にもあるように, 10進数では `10/3` や `10/7` などの循環小数があり, 無限の桁数がなければ正確な数値を保持できません(3倍,
7倍しても元の数値には戻らない)。同様に(コンピューター内でよく使わている) 2進数も, `0.1`
という浮動小数点数は循環小数になり正しい値を保持出ません。

  * 値の範囲が限定的であれば, 固定少数点数で扱う方法がある。例えば, 内部処理はずっと 100倍した値で扱い (`80 * 1`), アウトプット時に調整する, など。

  * 十進演算を行う方法 … 各種プログラミング言語で, 十進演算ライブラリーが用意されているはず。それらは計算精度が高い, もしくは計算精度を指定できることも多く, 誤差が出にくい  
参考:
[https://ja.wikipedia.org/wiki/任意精度演算](https://ja.wikipedia.org/wiki/%E4%BB%BB%E6%84%8F%E7%B2%BE%E5%BA%A6%E6%BC%94%E7%AE%97)

  * 例えば Pythonには [fractions --- 有理数](https://docs.python.org/ja/3/library/fractions.html) モジュールがあり, 分数の形で持つことで(計算途中までは)正しく値を保持できる可能性が高い (アウトプット時 小数点数に変換する際に誤差が出てくるかもだが) … この様なライブラリーを探すか作るかする


CC BY-SA 4.0
2021-11-06T09:30:39.287
83454
2021-11-06T09:30:39.287
43025
83451
answer


十進数の表現をそのまま計算したいと言うことであってますでしょうか？

Cの浮動小数点数の型である`float`、`double`、`long
double`はその実装形式や基数がいくつであるかは言語仕様では決められていません。しかし、ほとんどの環境では、[IEEE
754](https://ja.wikipedia.org/wiki/IEEE_754)の形式を採用しており、`float`はbinary32(単精度浮動小数点数)、`double`と`long
double`はbinary64(倍精度浮動小数点数)になっています(`long
double`が80ビットの拡張倍精度浮動小数点数になっている場合があります)。これらの浮動小数点数の形式は基数が2であるため、十進数の小数点数を正確に表現することはできません。つまり、`0.01`は0.01に極めて近い値であっても、正確に0.01では無いと言うことです。これは2の基数の浮動小数点数を使う限り、避ける方法がありません。(IEEE
754のbinary32やbinary64を採用している理由は、多くのCPUでこれらの形式に対して四則演算等ができる浮動小数点数ユニットを持っているからです。)

では、どうするかです。先程書きましたが、Cの言語仕様としては、基数が2で無ければならないというわけはなく、基数が10の場合についても言語仕様では言及されています。つまり、浮動小数点数の基数が10になるような環境を探せばいいと言うことです。と、言いたいところですが、基数が10になるような環境は見つけられませんでした。

もう標準で対応できる方法は無いのか…、というと、未来はあります。現在策定中の次のC言語規格であるC23(現在はC2xと言われている)では、基数が10の十進浮動小数点数の型である`_Decimal32`、`_Decimal64`、`_Decimal128`が規程される予定です。これらは既に出版済みのTS
18661-3でC言語の拡張として載っており、一部のコンパイラ(GCCやIntel
Compiler等)で部分的に実装されています。通常の`dobule`(binary64)では`0.1`と`0.2`を足しても`0.3`と等しくなりませんが、`_Decimal64`(decimal64)では、等しくなります。

[code]

    #include <stdio.h>
    int main(void)
    {
        double a = 0.1 + 0.2;
        _Decimal64 b = 0.1dd + 0.2dd;
        printf("%d\n", a == 0.3); // not equal => 0
        printf("%d\n", b == 0.3dd); // equal => 1
        return 0;
    }
    
[/code]

※最新のGCCやIntel Compiler等で試してください。古いバージョンやVisual
C++等ではエラーになる場合があります。Clangもまだ対応はしていないようです。Visual
Studioで試すとなると、[カスタムビルドでGCCを使う](https://stackoverflow.com/questions/14768073/how-
to-use-gcc-with-microsoft-visual-studio)といった方法が必要になるようです。

ただ、`printf`等で十進浮動小数点数を表示する`%Da`といった表現はGCCではまだ実装されていないようで、実用的にどこまで使えるかわかりません。また、CPUが十進浮動小数点数に対応した計算ユニットや命令に対応していない場合、通常の浮動小数点数に比べて非常に計算が遅くなる場合があります。なお、十進浮動小数点数にも精度があります。精度を越えて正確な計算はできませんので、その点はご理解ください。

`_Decimal64`等で対応していないコンパイラの場合は、Cの標準で直接扱う方法はありませんので、外部ライブラリを使うしかありません。軽く探してみましたが、[libdfp](https://github.com/libdfp/libdfp)というのがあるようです。任意精度が欲しい場合は[mpdecimal](https://www.bytereef.org/mpdecimal/)がいいかもしれません。


CC BY-SA 4.0
2021-11-07T00:16:21.870
83462
2021-11-08T13:05:35.067
2021-11-08T13:05:35.067
7347
7347
83451
answer


質問の範囲から多少外れますが参考情報として。

Visual Studio 2019とのことなので、[c](/questions/tagged/c "'c' のタグが付いた質問を表示")
言語ではありませんが [c++-cli](/questions/tagged/c%2b%2b-cli "'c++-cli' のタグが付いた質問を表示")
言語が使えます。C++/CLI言語は.NET Framework上で動作するため、10進演算を行う
[`System::Decimal`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.decimal?view=netframework-4.8) が標準で使えます。

[code]

    #include <stdio.h>
    #include <vcclr.h>
    
    static inline auto operator ""_m(const char* literal) {
        return System::Decimal::Parse(gcnew System::String(literal));
    }
    
    int main() {
        auto test1 = 80 * 0.01_m;
        pin_ptr<const wchar_t> str = PtrToStringChars(test1.ToString());
        wprintf(L"%s\n", str);
        return 0;
    }
    
[/code]

ちなみに.NET Frameworkの `System::Decimal` は有効精度も保持しているので、 `0.80` が得られます（例えば
`0.0100_m` に変更すると結果は `0.8000` になります）。


CC BY-SA 4.0
2021-11-07T06:30:03.790
83469
2021-11-08T13:04:18.003
2021-11-08T13:04:18.003
4236
4236
83451
answer