7

浮動小数点数の出力で厄介な問題に遭遇しました。Windowsで11.545を小数点以下2桁の精度でフォーマットすると、予想どおり「11.55」が出力されます。ただし、Linuxで同じことを行うと、出力は「11.54」です!

私はもともと Python でこの問題に遭遇しましたが、さらに調査したところ、違いは基盤となる C ランタイム ライブラリにあることがわかりました。(アーキテクチャはどちらの場合も x86-x64 です。) C の次の行を実行すると、Python と同様に、Windows と Linux で異なる結果が生成されます。

printf("%.2f", 11.545);

これをより明確にするために、数値を小数点以下 20 桁まで出力しました ( "%.20f"):

Windows: 11.54500000000000000000
Linux:   11.54499999999999992895

11.545 を 2 進数として正確に格納できないことはわかっています。そのため、Linux は実際に格納されている数値を可能な限り最高の精度で出力するのに対し、Windows はその数値の最も単純な 10 進数表現を出力します。ユーザーが何を意味する可能性が最も高いかを推測しようとします。

私の質問は、Windows で Linux の動作をエミュレートする (合理的な) 方法はありますか?

(Windows の動作は確かに直感的なものですが、私の場合、Windows プログラムの出力と Linux プログラムの出力を実際に比較する必要があり、変更できるのは Windows の出力だけです。ちなみに、私はの Windows ソースを見てくださいprintf。ただし、float->string 変換を行う実際の関数は で_cfltcvt_lあり、そのソースは利用できないようです。)

編集:プロットが厚くなります!これが不正確な表現によって引き起こされるという理論は間違っているかもしれませ'%.2f' % 0.125.

Windows: 0.13
Linux:   0.12

ただし、round(0.125, 2)Windows と Linux の両方で 0.13 を返します。

4

6 に答える 6

2

まず第一に、この場合、Windowsは間違っているように聞こえます(これは本当に重要ではありません)。C標準では、によって出力される値が適切な桁数に丸められる必要が%.2fあります。このための最もよく知られているアルゴリズムは、DavidM.Gayによって実装されたdtoaです。おそらくこれをWindowsに移植するか、ネイティブ実装を見つけることができます。

Steele and Whiteの「浮動小数点数を正確に印刷する方法」をまだ読んでいない場合は、コピーを見つけて読んでください。それは間違いなく啓発的な読み物です。70年代後半のオリジナルを必ず見つけてください。ある時点でACMまたはIEEEから購入したと思います。

于 2010-02-10T04:40:27.267 に答える
2

ここで、Windowsが特に巧妙なことをしているとは思いません(基数10のフロートを再解釈しようとするなど):最初の17桁の有効数字を正確に計算し(「11.545000000000000」になります)、余分なタックをしているだけだと思いますポイントの後に要求された桁数を構成するために最後にゼロ。

他の人が言ったように、0.125 の異なる結果は、Windows では四捨五入を使用し、Linux では四捨五入を使用しています。

Python 3.1 (および Python 2.7 が登場する場合) では、float をフォーマットした結果はプラットフォームに依存しないことに注意してください (例外的なプラットフォームを除く)。

于 2010-02-10T11:13:29.743 に答える
1

10進数モジュールを使用すると、いくつかの丸めモードにアクセスできます。

import decimal

fs = ['11.544','11.545','11.546']

def convert(f,nd):
    # we want 'nd' beyond the dec point
    nd = f.find('.') + nd
    c1 = decimal.getcontext().copy()
    c1.rounding = decimal.ROUND_HALF_UP
    c1.prec = nd
    d1 = c1.create_decimal(f)
    c2 = decimal.getcontext().copy()
    c2.rounding = decimal.ROUND_HALF_DOWN
    c2.prec = nd   
    d2 = c2.create_decimal(f)
    print d1, d2

for f in fs:
    convert(f,2)

intまたは文字列から小数を作成できます。あなたの場合、必要以上の桁数の文字列をフィードし、context.precを設定して切り捨てます。

10進モジュールの詳細な概要を含むpymotw投稿へのリンクは次のとおりです。

http://broadcast.oreilly.com/2009/08/pymotw-decimal---fixed-and-flo.html

于 2010-02-10T04:17:48.973 に答える
0

精度から十分に離れた数値の丸めに影響を与えない小さなデルタを減算(または負の数の場合は加算)してみることができます。

たとえば、で丸める場合は%.2f、Windowsで次のバージョンを試してください。

printf("%.2f", 11.545 - 0.001);

浮動小数点数は、内部で何が起こっているのかわからない場合、問題になることで有名です。その場合、最善の策は、問題を軽減するために10進型ライブラリを作成(または使用)することです。


サンプルプログラム:

#include <stdio.h>
int main (void) {
    printf("%.20f\n", 11.545);
    printf("%.2f\n", 11.545);
    printf("%.2f\n", 11.545 + 0.001);
    return 0;
}

私のCygwin環境でこれを出力します:

11.54499999999999992895
11.54
11.55

これは特定のケースでは問題ありませんが(間違った方向に進んでいますが、うまくいけば他の方向にも適用する必要があります。テストする必要があります)、これがすべての場合に機能することを確認したい場合は、可能な入力範囲全体を確認する必要があります。あなたの場合。


アップデート:

あなたのコメントに基づくEvgeny:

これはこの特定のケースでは機能しますが、一般的な解決策としては機能しません。たとえば、フォーマットしたい数値が11.545ではなく0.545の場合、'%.2f'%(0.545-0.001)は "0.54"を返しますが、Linuxの'%.2f'%0.545は正しく"0.55"を返します。

そのため、範囲全体をチェックして機能するかどうかを確認する必要があると述べ、10進数のデータ型が望ましいと述べたのはそのためです。

小数の精度が必要な場合は、それを実行する必要があります。しかし、Linuxが逆方向に進むその範囲のケースを検討することをお勧めします(あなたのコメントによると)-LinuxとWindowsがあなたが見つけたものと反対の方向に一致しない状況があるかもしれません-おそらく10進タイプが勝ちましたそれを解決しないでください。

比較ツールは、最終的な小数部の1の違いを無視できるため、もう少しインテリジェントにする必要がある場合があります。

于 2010-02-10T04:16:45.950 に答える
0

切り捨てを強制するために、値からわずかな量を差し引くことができる場合があります

print "%.2f"%(11.545-1e-12)
于 2010-02-10T04:59:22.997 に答える
0

代わりに、浮動小数点数を許容範囲/イプシロンと比較することを検討してください。これは、正確に一致させようとするよりもはるかに堅牢です。

私が言いたいのは、次の場合に 2 つの浮動小数点数が等しいということを除いて、次のとおりです。

f1 == f2

次の場合に等しいとします。

fabs(f1 - f2) < eps

いくつかの小さなeps。この問題の詳細については、こちらを参照してください

于 2010-02-10T04:02:52.033 に答える