6

float (秒を表す) と int64 (ナノ秒を表す) の間で変換し、float から小数点以下の桁数を取得するコードがあります。6

int64_t nanos = f * 1000000000LL;

ただし、浮動小数点数に格納されている多くの 10 進数値は、2 進浮動小数点数で正確に表すことができないため14199999488、浮動小数点数が14.2f. 現在、基数の後の有効桁数を計算することで、この問題を解決しています

const float logOfSecs = std::log10(f);

int precommaPlaces = 0;
if(logOfSecs > 0) {
   precommaPlaces = std::ceil(logOfSecs);
}

int postcommaPlaces = 7 - precommaPlaces;
if(postcommaPlaces < 0) {
   postcommaPlaces = 0;
}

そして、フロートを文字列に出力して、Qt がフロートを正しく丸めるようにします。次に、文字列を解析して前後のコンマ整数にし、それらを整数演算で乗算します。

const QString valueStr = QString::number(f, 'f', postcommaPlaces);
qint64 nanos = 0;
nanos += valueStr.section(".", 0, 0).toLongLong() * 1000000000LL;
if(postcommaPlaces) {
   nanos += valueStr.section(".", 1).toLongLong() * 
     std::pow(10.0, 9 - postcommaPlaces);
}

これはうまくいきますが、これを行うためのより良い、おそらくより速い方法があるかどうか疑問に思っていましたか?

4

3 に答える 3

3

ダメージがすでに与えられている値を格納することでfloat、元の数値が何であれ失われます。意図された可能性のある値を推測してから丸めることができます。または、単にユーザーに値を表示しようとしている場合は、小数点以下の桁数を減らすことができます。

int64_t代わりに、コード ベース全体で固定小数点表現を使用することで、これらの問題をすべて解決できますfloat。変換のたびに精度が失われることはありません。

于 2013-01-10T16:31:21.293 に答える
2

たとえば、小数点以下1桁に丸めたい場合

#include <iostream>

int main()
{
    float f = 14.2f;
    long long n = f * 1000000000LL;
    std::cout << "float: " << n << '\n';
    n = (f + 0.05) * 10;
    n *= 100000000LL;
    std::cout << "rounded: " << n << '\n';
    return 0;
}

小数点以下 2 桁の(f + 0.005) * 100場合は、...、小数点以下 6 桁の場合

n = ((long long)((f + 0.0000005) * 1000000)) * 1000LL;

有効桁数(すべての桁)を考慮したい場合は、まず小数点以下log10(f)を丸めて調整する必要があります。

しかし、@MarkBがすでに言ったようint64_tに、そもそも使用する場合、これはまったく必要ありません。

于 2013-01-10T16:32:39.217 に答える
0

他の回答で述べたように、任意の 10 進数への丸めは、フロートの出力と密接に関連しています。正しく丸めを行うアルゴリズムはかなり複雑であるため、正しく行う最も簡単な方法は、printf 自体を使用することです。

必ずしも任意の桁数を指定する必要はないことに注意してください。別の方法として、基数 2 に変更せずに変換される最短の 10 進数を使用することもできます。このようなアルゴリズムは、Scheme、Java、Python、Squeak/ で float を出力するために使用されます。 Pharo など ... 残念ながら、libm printf も標準 C ライブラリも準拠していません。

スキームは、固定桁数を課すときに桁が重要ではない * を出力するため、さらに優れています (* は、基数 2 に戻すと、どの桁も同じ浮動小数点数になることを意味します)。

この問題http://code.google.com/p/pharo/issues/detail?id=4957には Float-asMinimalDecimalFraction.st という名前の添付ファイルがあり、Scheme と同様の印刷アルゴリズムの Smalltalk での実装が含まれていますが、分数 (2 つの任意の長さの整数の比率) ではなく、ASCII 文字列。

したがって、たとえば、14.2f は内部的に 14.19999980926513671875 と正確に表されますが、遅すぎることはありません。正しく丸められる最小の小数は (142/10) であることがわかります。

Smalltalk でそのようなコードを使用すると、問題の解決策は簡単に次のようになります。

nanos := (floatingPointSeconds asMinimalDecimalFraction * 1e9) rounded.

しかし、上記のコードは、内部で正確な算術演算 (1e9は整数) と任意の長さの整数を使用しています。

float で乗算を実行するのは良くないことに注意してください。

nanos := (aFloat * 1e9) asMinimalDecimalFraction rounded.

実際、1e9 asFloat 変換は正確ですが、仮数部は 21 ビットに及ぶため、浮動小数点数の乗算はおそらく丸め誤差を累積し、短い小数を取得する問題を悪化させます。

質問に対して技術的に何らかの形で回答していますが、個人的には、上記のアルゴリズムは次の理由から実用的に不適切であると考えています。

  1. 任意精度の算術ライブラリの助けを借りずに低レベルの C/C++ 命令でそれを行うことは、結果への最速のパスではありません

  2. いくつかの丸め誤差のある計算の結果には適用されないため、非常に制限されています (統計的に多くの桁数が必要です)。

  3. 単純に Float の使用を避け、nanos int で作業できる場合はやり過ぎです。

それにもかかわらず、それが存在することを知ることは常に素晴らしいことです...

于 2013-01-10T21:12:09.687 に答える