web-dev-qa-db-ja.com

文字列から浮動小数点数を手動で解析する方法

もちろん、ほとんどの言語にはこのためのライブラリ関数がありますが、自分でやりたいとしましょう。

フロートがCまたはJavaプログラム(「f」または「d」サフィックスを除く)のように、たとえば「4.2e1」、「.42e2」、または単に「42」として指定されているとします。 。一般に、小数点の前に「整数部分」、小数点の後に「小数部分」、「指数」があります。3つはすべて整数です。

個々の数字を見つけて処理するのは簡単ですが、精度を失うことなく、どのようにしてそれらをタイプfloatまたはdoubleの値に構成しますか?

整数部分に10 ^nを掛けることを考えています。ここでnは小数部分の桁数であり、小数部分を整数部分に加算し、指数からnを減算します。これにより、たとえば、4.2e142e0に効果的に変換されます。次に、pow関数を使用して10 ^exponentを計算し、結果に新しい整数部分を掛けることができます。問題は、この方法が全体を通して最大の精度を保証するかどうかです。

これについて何か考えはありますか?

34
Thomas

2進表現を使用して、浮動小数点数を直接アセンブルします。

数字を次々と読み、最初にすべての数字を見つけます。整数演算でそれを行います。また、小数点と指数を追跡します。これは後で重要になります。

これで、浮動小数点数を組み立てることができます。最初に行うことは、最初に設定された1ビット(最高から最低)の数字の整数表現をスキャンすることです。

最初の1ビットの直後のビットが仮数です。

指数を取得することも難しくありません。科学的記数法から、最初の1ビットの位置、小数点の位置、およびオプションの指数を知っています。それらを組み合わせて、浮動小数点指数バイアスを追加します(127だと思いますが、参考文献を確認してください)。

この指数は、0から255の範囲内にある必要があります。それが大きいまたは小さい場合は、正または負の無限数になります(特別な場合)。

指数をフロートのビット24から30にそのまま格納します。

最上位ビットは単に符号です。 1は負を意味し、0は正を意味します。

実際よりも説明するのは難しいです。浮動小数点数を分解して、指数と仮数を見てみると、それが本当に簡単であることがわかります。

ところで、浮動小数点自体で算術演算を行うのは悪い考えです。仮数を常に23の有効ビットに切り捨てるからです。そのように正確に表現することはできません。

11

他のすべての答えは、これを適切に行うことがいかに難しいであるかを見逃しています。これである程度正確なファーストカットアプローチを行うことができますが、IEEE丸めモード(など)を考慮するまで、正しい答えは得られません。 。私は以前、かなり多くのエラーを伴う単純な実装を作成しました。

数学が怖くない場合は、David Goldbergによる次の記事を読むことを強くお勧めします すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと 。内部で何が起こっているのか、そしてなぜビットがそのように配置されているのかについて、よりよく理解することができます。

私の最善のアドバイスは、実際のatoi実装から始めて、そこから移動することです。足りないものがすぐに見つかりますが、 strtod のソースを見ると、正しいパス(長い、長いパス)にたどり着きます。最終的には、標準ライブラリがあることをここにdietyを挿入と称賛します。

/* use this to start your atof implementation */

/* atoi - [email protected] */
/* PUBLIC DOMAIN */
long atoi(const char *value) {
  unsigned long ival = 0, c, n = 1, i = 0, oval;
  for( ; c = value[i]; ++i) /* chomp leading spaces */
    if(!isspace(c)) break;
  if(c == '-' || c == '+') { /* chomp sign */
    n = (c != '-' ? n : -1);
    i++;
  }
  while(c = value[i++]) { /* parse number */
    if(!isdigit(c)) return 0;
    ival = (ival * 10) + (c - '0'); /* mult/accum */
    if((n > 0 && ival > LONG_MAX)
    || (n < 0 && ival > (LONG_MAX + 1UL))) {
      /* report overflow/underflow */
      errno = ERANGE;
      return (n > 0 ? LONG_MAX : LONG_MIN);
    }
  }
  return (n>0 ? (long)ival : -(long)ival);
}
23
user7116

10進数を最良の浮動小数点近似に変換するための「標準」アルゴリズムは、William Clingerの 浮動小数点数を正確に読み取る方法ここ からダウンロードできます。これを正しく行うには、コーナーケースを処理するために、少なくとも一定の割合の時間で、複数精度の整数が必要であることに注意してください。

逆に、浮動小数点数から最適な10進数を印刷するためのアルゴリズムは、Burger and Dybvigの 浮動小数点数をすばやく正確に印刷する 、ダウンロード可能 ここ にあります。これには、多倍長整数演算も必要です

双方向のアルゴリズムについては、David M Gayの 正しく丸められた2進化10進数および10進数2進数の変換 も参照してください。

19
Peter S. Housel

はい、構造を浮動小数点演算に分解できます限りこれらの演算は[〜#〜]正確[〜#〜]、そして、単一の最終的な不正確操作を行う余裕があります。

残念ながら、浮動小数点演算soonは不正確になり、仮数の精度を超えると、結果は丸められます。丸め誤差が導入されると、それは以降の操作で累積されます...
したがって、一般的に[〜#〜] no [〜#〜]、このような単純なアルゴリズムを使用して任意の小数を変換することはできません。これにより、数値が誤って丸められる可能性があります。他の人がすでにあなたに言ったように、正しいもののいくつかのulpによってオフ。

しかし、どのように遠いのか見てみましょうWE CAN GO:

このようにフロートを注意深く再構築すると、次のようになります。

if(biasedExponent >= 0)
    return integerMantissa * (10^biasedExponent);
else
    return integerMantissa / (10^(-biasedExponent));

integerMantissaが桁数が多い場合に累積する場合と、10をbiasedExponentの累乗にする場合の両方で、精度を超えるリスクがあります。

幸い、最初の2つの演算が正確である場合、最終的な不正確な演算*または/を使用できます。これは、IEEEプロパティのおかげで、結果は正しく丸められます。

これを24ビットの精度を持つ単精度浮動小数点数に適用してみましょう。

10^8 > 2^24 > 10^7

2の倍数は指数を増加させ、仮数を変更しないことに注意してください。10の指数に対して5の累乗を処理するだけで済みます。

5^11 > 2^24 > 5^10

ただし、integerMantissaで7桁の精度と、-10から10の間のbiasedExponentを使用できます。

倍精度では、53ビット、

10^16 > 2^53 > 10^15
5^23 > 2^53 > 5^22

したがって、10進数で15桁、および-22から22の間のバイアスされた指数を使用できます。

数値が常に正しい範囲に収まるかどうかを確認するのはあなた次第です...(本当にトリッキーな場合は、末尾のゼロを挿入/削除することで仮数と指数のバランスをとることができます)。

それ以外の場合は、拡張精度を使用する必要があります。
あなたの言語が任意精度の整数を提供する場合、それを正しくするのは少し難しいですが、それほど難しくはありません。私はこれをSmalltalkで行い、 http://smallissimo.blogspot。 fr/2011/09/clarifying-and-optimizing.html および http://smallissimo.blogspot.fr/2011/09/reviewing-fraction-asfloat.html

これらは単純で単純な実装であることに注意してください。幸い、libcはより最適化されています。

2
aka.nice

解析時に小数を無視できます(その場所を除く)。入力が次のようになっているとします。156.7834e10...これは、整数1567834に続いてe10に簡単に解析できます。これは、小数がフロートの「数値」部分の末尾から4桁であるため、e6に変更します。 。

精度が問題です。使用している言語のIEEE仕様を確認する必要があります。仮数(または分数)のビット数が整数型のビット数よりも大きい場合、次のような数値を入力すると精度が低下する可能性があります。

5123.123123e0-このメソッドでは5123123123に変換されます。これは整数には収まりませんが、5.123123123のビットはfloat仕様の仮数に収まる可能性があります。

もちろん、小数点の前の各桁を取得し、現在の合計(浮動小数点数)に10を掛けてから、新しい桁を追加する方法を使用することもできます。小数点以下の桁の場合は、現在の合計に加算する前に、桁に10の累乗を掛けます。ただし、この方法では、すぐに利用できる解析ライブラリを使用せずに浮動小数点プリミティブを使用する必要があるため、なぜこれを行うのかという疑問が生じます。

とにかく、頑張ってください!

2
billjamesdev

私の最初の考えは、仮数の最初の18桁のみを使用して、文字列をint64仮数とint10進指数に解析することです。たとえば、1.2345e-5は12345と-9に解析されます。次に、仮数に10を掛け、仮数が18桁の長さ(> 56ビットの精度)になるまで指数を減らし続けます。次に、テーブルで10進数の指数を調べて、数値を10進数のn * 10 ^ mから2進数のp * 2 ^ q形式に変換するために使用できる係数と2進数の指数を見つけます。係数は別のint64になるので、仮数にそれを掛けて、結果の128ビット数の上位64ビットを取得します。このint64仮数は、必要な精度のみを失うフロートにキャストでき、2 ^ q指数は、精度を失うことなく乗算を使用して適用できます。

これは非常に正確で非常に高速であると思いますが、特別な数値NaN、-infinity、-0.0、およびinfinityを処理することもできます。非正規化数や丸めモードについては考えていません。

1
Jon Harrop

そのためには、適切なバイナリ表現のために標準のIEEE754を理解する必要があります。その後、Float.intBitsToFloatまたはDouble.longBitsToDoubleを使用できます。

http://en.wikipedia.org/wiki/IEEE_754

0
Jorge Ferreira

可能な限り最も正確な結果が必要な場合は、より高い内部作業精度を使用してから、結果を目的の精度にダウンコンバートする必要があります。いくつかのULPのエラーを気にしない場合は、必要に応じて、必要な精度で10を繰り返し乗算できます。 pow()関数は、大きな指数に対して不正確な結果を生成するため、避けます。

0
Adam Rosenfield

数値を表す任意の文字列を、精度を失うことなくdoubleまたはfloatに変換することはできません。 2進数のfloatまたはdoubleでのみ概算できる、10進数(「0.1」など)で正確に表すことができる多くの小数があります。これは、分数1/3を正確に小数で表すことができない方法と似ています。0.333333としか記述できません...

ライブラリ関数を直接使用したくない場合は、それらのライブラリ関数のソースコードを見てみませんか?あなたはJavaについて言及しました。ほとんどのJDKには、クラスライブラリのソースコードが付属しているため、Java.lang.Double.parseDouble(String)メソッドがどのように機能するかを調べることができます。もちろん、BigDecimalのようなものは、精度と丸めモードの制御に適していますが、floatまたはdoubleである必要があるとおっしゃいました。

0
sk.