5

今日、私はかなり奇妙な問題に遭遇しました。数値の文字列の長さを計算する必要があったので、この解決策を思いつきました

// say the number is 1000
(int)(log(1000)/log(10)) + 1

これは数式に基づいています

log10 x = logn x/logn10 (ここで説明)

しかし、Cでは、

(int)(log(1000)/log(10)) + 1

等しくない_

(int) log10(1000) + 1

しかし、そうあるべきです。

このコードを使用してJavaで同じことを試しました

(int) (Math.log(1000) / Math.log(10)) + 1
(int) Math.log10(1000) + 1

しかし、それは同じ間違った方法で動作します。

話は続きます。このコードを実行した後

for (int i = 10; i < 10000000; i *= 10) {
   System.out.println(((int) (Math.log10(i)) + 1) + 
                " " + ((int) (Math.log(i) / Math.log(10)) + 1));
}

私は得る

2 2
3 3
4 3  // here second method produces wrong result for 1000
5 5
6 6
7 6  // here again

したがって、バグは 1000 の倍数ごとに発生するようです。

これをCの先生に見せたら、対数除算時の型変換ミスが原因かもしれないと言われましたが、原因がわかりませんでした。

だから私の質問は

  • 数学によると、そうあるべきなのに、なぜ は(int) (Math.log(1000) / Math.log(10)) + 1と等しくないのですか。(int) Math.log10(1000) + 1
  • 1000の倍数だけ間違っているのはなぜですか?

編集:丸め誤差ではありません。

Math.floor(Math.log10(i)) + 1
Math.floor(Math.log(i) / Math.log(10)) + 1

同じ間違った出力を生成する

2 2
3 3
4 3
5 5
6 6
7 6

edit2:桁数を知りたいので切り捨てます。

log10(999) + 1 = 3.9995654882259823
log10(1000) + 1 =  4.0

丸めただけでも同じ結果 (4) が得られますが、999 は 3 桁なので間違っています。

4

8 に答える 8

23

コード スニペットを提供しました

for (int i = 10; i < 10000000; i *= 10) {
   System.out.println(((int) (Math.log10(i)) + 1) + 
                " " + ((int) (Math.log(i) / Math.log(10)) + 1));
}

あなたの質問を説明するために。キャストを削除してint、ループを再度実行してください。受け取ります

2.0 2.0
3.0 3.0
4.0 3.9999999999999996
5.0 5.0
6.0 6.0
7.0 6.999999999999999

あなたの質問にすぐに答えます。tliff が既に主張したように、キャストは適切に丸める代わりに小数を切り捨てます。

編集:を使用するように質問を更新しましfloor()たが、キャストのようfloor()切り捨てられるため、小数点以下が削除されます!

于 2009-09-30T11:05:37.353 に答える
8

ログ操作は超越関数です。結果を評価するためにコンピューターができる最善の方法は、必要な演算を近似する代数関数を使用することです。結果の精度は、コンピューターが使用するアルゴリズムに依存します (これは FPU のマイクロコードである可能性があります)。Intel FPU には、さまざまな超越関数 (三角関数も超越関数) の精度に影響を与える設定があり、FPU の仕様には、使用されるさまざまなアルゴリズムの精度レベルが詳述されています。

したがって、上記の丸め誤差に加えて、計算された log(x) が実際の log(x) と等しくない可能性があるため、精度の問題もあります。

于 2009-09-30T11:15:49.753 に答える
5

これ、精度と丸めの問題によるものです。 Math.log(1000) / Math.log(10)は 3 と正確には等しくありません。

正確な精度が必要な場合は、浮動小数点演算を使用しないでください。対数全般をあきらめてください。浮動小数点数は本質的にあいまいです。正確な結果を得るには、整数演算を使用してください。

一般的にこの道をたどらないことを本当にお勧めしますが、整数の対数をとって大きさを決定しているように思えます。その場合、 (int)(Math.log(x+0.5) / Math.log(10))より安定しdoubleますが、 の精度は 53 ビットしかないため、10 前後の 15 番目の double は整数を正確に表すことができなくなり、このトリックは機能しません。

于 2009-09-30T11:11:24.380 に答える
4

Skizz によって指摘された精度の問題を回避するには、分子に非常に小さな値を追加します。

// say the number is 1000
(int)((log(1000)+1E-14)/log(10)) + 1

精度を元に戻すには、1E-14 で十分です。

小さな値を 1E-15 から変更しました。これにより、一部の入力で誤った結果が得られました。

s のランダム サンプルに対して 1E-14 でテストしたunsigned long longところ、すべての数値が合格しました。

于 2009-09-30T11:30:36.173 に答える
2

更新:精度と丸め誤差が原因です

于 2009-09-30T10:56:57.630 に答える
0

結果を整数にしたい場合は、ポイントごとに切り取るのではなく、おそらく丸める必要があります。

おそらく6.999999のようなものを取得し、それを6に切り下げます。

于 2009-09-30T10:59:05.083 に答える
0

(int) キャストを使用すると、必要な小数部分が切り捨てられます。キャストせずに double として出力してみてください (なぜキャストするのですか?)。

于 2009-09-30T11:05:18.477 に答える
0

中間結果、つまり log(1000)、log(10)、log(1000)/log(10)、および log10(1000) を出力します。これにより、推測よりも優れたヒントが得られるはずです。

于 2009-09-30T11:08:22.740 に答える