web-dev-qa-db-ja.com

フロートに可能な限り小さいフロートを追加する

フロートに可能な限り小さい値をフロートに追加したい。したがって、たとえば、1.0 +可能な限り最小の浮動小数点数を取得するためにこれを実行しようとしました。

float result = 1.0f + std::numeric_limits<float>::min();

しかし、それを行った後、次の結果が得られます。

(result > 1.0f) == false
(result == 1.0f) == true

Visual Studio 2015を使用しています。これはなぜ起こるのですか?それを回避するにはどうすればよいですか?

59
Squidy

1の後の次の表現可能な値が必要な場合は、 std::nextafter 、から<cmath>ヘッダー。

float result = std::nextafter(1.0f, 2.0f);

最初の引数から2番目の引数の方向に向かって、次の表現可能な値を返します。したがって、1未満の次の値を見つけたい場合は、これを行うことができます。

float result = std::nextafter(1.0f, 0.0f);

1と次の表現可能な値の差が0と次の表現可能な値の差よりも大きいため、最小の正の表現可能な値を1に追加しても機能しません。

86

観察している「問題」は、浮動小数点演算の性質によるものです。

FP精度はスケールに依存します;値_1.0_を中心に、精度は_1.0_と_1.0+min_representable_を区別するのに十分ではありません。ここで_min_representable_は、ゼロよりも大きい可能な最小値です(最小の正規化数std::numeric_limits<float>::min()...のみを考慮しても、最小の非正規化は数桁小さくなります)。

たとえば、倍精度の64ビットIEEE754浮動小数点数の場合、_x=10000000000000000_(1016xと_x+1_を区別することはできません。


解像度がスケールとともに変化するという事実は、小数点が「浮動」するため、「浮動小数点」という名前のまさにその理由です。代わりに、固定小数点表現の解像度は固定されます(たとえば、1/65536〜0.00001の精度を持つ単位の下に16桁の2進数がある場合)。

たとえば、IEEE754 32ビット浮動小数点形式では、符号に1ビット、指数に8ビット、仮数に31ビットがあります。

floating point


_1.0f + eps != 1.0f_が_FLT_EPSILON_、または _std::numeric_limits<float>::epsilon_ として事前定義された定数として使用可能な最小値epsWikipediaのマシンイプシロン も参照してください。これは、イプシロンと丸め誤差との関係について説明しています。

つまりイプシロンは、ここで期待していたことを行う最小の値であり、1.0に追加すると違いが生じます。

これのより一般的なバージョン(1.0以外の数の場合)は、(仮数の)最後の場所で1ユニットと呼ばれます。ウィキペディアの LP記事 を参照してください。

41
6502

minは、(正規化形式の)フロートが想定できるゼロ以外の最小値、つまり2前後の値です。-126 (-126は浮動小数点数の最小許容指数です); floatは仮数部が23ビットしかないため、1に合計すると1になります。そのため、そのような小さな変更はそのような「大きな」数値では表現できません(変更の合計2を表示する126ビットの仮数-126 1)に。

代わりに、1に可能な最小変更はepsilon(いわゆるマシンイプシロン)です。これは実際には2です。-23 -仮数の最後のビットに影響するため。

20
Matteo Italia

浮動小数点値を可能な限り最小の量だけ増減させるには、nextafterを+/- infinity()に向けて使用します。

next_after(x,std::numeric_limits::max())を使用する場合、xが無限大の場合の結果は間違っています。

#include <iostream>
#include <limits>
#include <cmath>

template<typename T>
T next_above(const T& v){
    return std::nextafter(v,std::numeric_limits<T>::infinity()) ;
}
template<typename T>
T next_below(const T& v){
    return std::nextafter(v,-std::numeric_limits<T>::infinity()) ;
}

int main(){
  std::cout << "eps   : "<<std::numeric_limits<double>::epsilon()<< std::endl; // gives eps

  std::cout << "after : "<<next_above(1.0) - 1.0<< std::endl; // gives eps (the definition of eps)
  std::cout << "below : "<<next_below(1.0) - 1.0<< std::endl; // gives -eps/2

  // Note: this is what next_above does:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::infinity()) << std::endl; // gives inf

  // while this is probably not what you need:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308

}
4
Johan Lundberg