9

以前は主に整数を使用していましたが、float または double を整数に切り詰める必要がある場合は、以前は次のように使用していました。

(int) someValue

私が次のことを知るまでを除いて:

NSLog(@"%i", (int) ((1.2 - 1) * 10));     // prints 1
NSLog(@"%i", (int) ((1.2f - 1) * 10));    // prints 2

(説明については、C# で float を int にキャストするときの奇妙な動作を参照してください)。

簡単な質問は、float または double を整数に適切に切り捨てるにはどうすればよいかということです。(この場合、「丸め」ではなく切り捨てが必要です)。または、1 つの数値が 1.9999999999999 であり、もう 1 つの数値が 2.00000000000001 であるため (大まかに言えば)、切り捨ては実際には正しく行われていると言えます。問題は、float や double をどのように変換すれば、結果が一般的な使用法で意味のある "切り捨てられた" 数値になるかということです。

round(この場合、 forの代わりに1.8の結果が必要なため、を使用するつもりはありません)12


より長い質問:

使った

int truncateToInteger(double a) {
    return (int) (a + 0.000000000001);
}

-(void) someTest {
    NSLog(@"%i", truncateToInteger((1.2 - 1) * 10));
    NSLog(@"%i", truncateToInteger((1.2f - 1) * 10));
}

どちらも として出力されます2が、ハックが多すぎるようです。「不正確さを取り除く」には、どの小さな数字を使用すればよいでしょうか? そのような恣意的なハックの代わりに、より標準的または研究された方法はありますか?

(たとえば、秒数が 90 または 118 の場合、何分と何秒経過したかを示す場合、分は として表示する必要があります1が、そうすべきではありません。に切り上げる2

4

7 に答える 7

12

もちろん、切り捨ては正しく実行されていますが、中間値が不正確です。

一般に、1.999999結果がわずかに不正確であるか2(切り捨て後の正確な数学の結果は2)、またはわずかに不正確であるか1.999998(切り捨て後の正確な数学の結果は ) を知る方法はありません1

さらに言えば、一部の計算では、わ​​ずか2.000001に不正確な1.999998. あなたが何をするにしても、あなたはそれを間違えます。切り捨ては非連続関数であるため、どのように実行しても、全体的な計算が数値的に不安定になります。

いずれにせよ、任意の許容範囲を追加できます: (int)(x > 0 ? x + epsilon : x - epsilon). あなたが何をしているかに応じて、それが役立つ場合とそうでない場合があります。これが「ハック」である理由です。epsilonは定数にすることも、 のサイズに応じてスケーリングすることもできxます。

2 番目の質問に対する最も一般的な解決策は、「不正確さを取り除く」ことではなく、不正確な結果を正確であるかのように受け入れることです。したがって、浮動小数点単位が 1.999999 であると言う場合(1.2-1)*10、OK、それ1.999999 です。その値が分数を表す場合は、1 分 59 秒に切り捨てられます。最終的に表示される結果は、真の値から 1 秒ずれます。それよりも正確な最終表示結果が必要な場合は、浮動小数点演算を使用して計算するべきではありません。または、分に切り捨てる前に最も近い秒に丸める必要があります。

浮動小数点数から不正確さを「除去」しようとする試みは、実際には不正確さを移動させるだけです.入力によってはより正確な結果が得られますが、他の入力では不正確になります. 不正確さが気にしない入力にシフトされる場合、または計算を実行する前に除外できる場合に幸運であれば、あなたは勝ちます。ただし、一般的に、入力を受け入れる必要がある場合は、どこかで負けます。最後の切り捨てステップで不正確さを取り除こうとするのではなく、計算をより正確にする方法を検討する必要があります。

あなたの例の計算には簡単な修正があります.10進数の小数点以下1桁の固定小数点演算を使用してください。形式が 1.2 を正確に表すことができることはわかっています。したがって、 を書き込む代わりに(1.2 - 1) * 10、計算を再スケーリングして 10 分の 1 を使用し (write (12 - 10) * 10)、最終結果を 10 で割って単位に戻す必要があります。

于 2012-06-28T12:44:29.870 に答える
3

質問を修正したので、問題は次のように思われます: いくつかの入力 x が与えられた場合、値 f'(x) を計算します。f'(x) は、正確な数学関数 f(x) の計算された近似値です。trunc(f(x)) を計算します。つまり、f(x) よりもゼロから遠く離れずに、ゼロから最も遠い整数 i を計算します。f'(x) には何らかのエラーがあるため、f(x) が 2 でも f'(x) が 0x1.ffffffffffffffp0 の場合など、trunc(f'(x)) は trunc(f(x)) と等しくない場合があります。f'(x) が与えられた場合、どのように trunc(f(x)) を計算できますか?

この問題は解決できません。すべての f に有効な解決策はありません。

解決策がない理由は、f' のエラーにより、f(x) が 0x1.ffffffffffffffp0 であるため f'(x) が 0x1.ffffffffffffp0 になるか、f'(x) が 0x1.ffffffffffffffp0 になる可能性があるためです。 f(x) が 2 であっても計算エラーが発生します。したがって、f'(x) の特定の値が与えられた場合、trunc(f(x)) が何であるかを知ることは不可能です。

f (および f' で近似するために使用される実際の操作) に関する詳細な情報が与えられた場合にのみ、解決策が可能です。あなたはその情報を提供していないので、あなたの質問には答えられません。

ここに仮説があります: f(x) の性質が、その結果が常に q の非負の倍数になるようなものであると仮定します。1 を割る q があるとします。たとえば、q は .01 (座標値の 100 分の 1) である可能性があります。または 1/60 (f は分の単位であるため、秒の単位を表します)。また、f' の計算に使用される値と操作が、f' の誤差が常に q/2 未満になるようなものであるとします。

この非常に限定された仮説的なケースでは、trunc(f'(x)+q/2) を計算することで trunc(f(x)) を計算できます。証明: i = trunc(f(x)) とする。i > 0 とすると、i <= f(x) < i+1 なので、i <= f(x) <= i+1-q (f(x) は q によって量子化されるため) となります。次に、iq/2 < f'(x) < i+1-q+q/2 (f'(x) が f(x) の q/2 内にあるため) です。その場合、i < f'(x)+q/2 < i+1 です。次に、trunc(f'(x)+q/2) = i となり、目的の結果が得られます。i = 0 の場合、-1 < f(x) < 1 なので、-1+q <= f(x) <= 1-q なので、-1+qq/2 < f'(x) < 1-q+q/2 なので、-1+q < f'(x)+q/2 < 1 なので、trunc(f'(x)+q/2) = 0.

(注: q/2 が使用される浮動小数点精度で正確に表現できない場合、またはエラーなしで f'(x) に簡単に追加できない場合は、証明、その条件、または加算のいずれかでいくつかの調整を行う必要があります。 q/2 の)

そのケースが目的に合わない場合は、f および f' の計算に使用される操作と値に関する詳細な情報を提供することによって期待される答えを期待することはできません。

于 2012-06-28T13:58:49.487 に答える
1

一般に、結果が入力よりも高い精度を期待するべきではないことをお勧めします。したがって、あなたの例では、浮動小数点数には小数点以下 1 桁があり、結果をそれ以上深刻にする必要はありません。

では、小数点第 1 位を四捨五入してから int に変換するのはどうでしょうか。

float a = (1.2f - 1) * 10;
int b;

// multiply by 10 to "round to one decimal place"
a = round( a * 10. );

// now cast to integer first to avoid further decimal errors
b = (int) a;

// get rid of the factor 10 again by integer division
b = b / 10;

// now 'b' should hold the result you're expecting;
于 2012-06-28T14:03:30.310 に答える
1
NSLog(@"%i", [[NSNumber numberWithFloat:((1.2 - 1) * 10)] intValue]); //2
NSLog(@"%i", [[NSNumber numberWithFloat:(((1.2f - 1) * 10))] intValue]); //2 
NSLog(@"%i", [[NSNumber numberWithFloat:1.8] intValue]); //1
NSLog(@"%i", [[NSNumber numberWithFloat:1.8f] intValue]); //1
NSLog(@"%i", [[NSNumber numberWithDouble:2.0000000000001 ] intValue]);//2
于 2012-06-28T14:14:01.710 に答える
1

「ハック」はそれを行う適切な方法です。float がどのように機能するかは簡単です。より適切な小数の動作NSDecimal(Number)が必要な場合は、

于 2012-06-28T12:43:28.890 に答える
0

予想されるエラーを計算する必要があり、切り捨てのためにそれを追加しても安全です。たとえば、1.8 は 1 にマッピングする必要があるとおっしゃいましたが、1.9 はどうでしょうか。1.99はどうですか?問題のドメインで 1.8 より大きな値が得られないことがわかっている場合は、切り捨てを機能させるために 0.001 を追加しても安全です。

于 2012-06-28T12:37:26.937 に答える
0

正しい方法は次のとおりです。実行する各浮動小数点演算を識別します。これには、10 進数を浮動小数点数に変換することも含まれます (たとえば、ソース テキストの「1.2」は浮動小数点値 0x1.3333333333333p0 を生成し、「1.2f」は 0x1.333334p0 を生成します)。各操作で発生する可能性のあるエラーの制限を決定します。(単純な算術演算など、IEEE 754 で定義された基本演算の場合、この制限は、実際の入力の数学的に正確な結果の 1/2 ULP [最小精度の単位] です。10 進数から 2 進浮動小数点への変換の場合、言語は仕様では 1 ULP が許容される場合がありますが、優れたコンパイラでは 1/2 ULP に制限されます. 正弦関数や対数関数などの複雑な関数を提供するライブラリ ルーチンの場合、商用ライブラリでは通常、いくつかの ULP の誤差が許容されますが、基本区間内ではより優れていることがよくあります. ライブラリ ベンダーから仕様を取得する必要があります。) 数学的な証明を使用して、最終的なエラーの範囲を決定します。ある誤差範囲 e について、正確な数学的結果が整数 i である場合、実際に計算された結果は半開区間 [つまり、i+1-e) にあることを証明できれば、次の値を生成できます。計算結果に e を加算し、その計算結果を切り捨てて整数にすることにより、正確な数学的結果を得ることができます。(簡潔にするために、特定の複雑さを省略しました。1 つは、e を追加すると i+1 に切り上げられる可能性があるという問題です。もう 1 つは、誤検知を回避することです。つまり、結果が i でない場合に i を生成しないようにすることです。実際の結果はそうではありません。計算結果を [ie, i+1-e) に入れるかもしれません。) ) 数学的な証明を使用して、最終的なエラーの境界を決定します。ある誤差範囲 e について、正確な数学的結果が整数 i である場合、実際に計算された結果は半開区間 [つまり、i+1-e) にあることを証明できれば、次の式を作成できます。計算結果に e を加算し、その計算結果を切り捨てて整数にすることにより、正確な数学的結果を得ることができます。(簡潔にするために、特定の複雑さを省略しました。1 つは、e を追加すると i+1 に切り上げられる可能性があるという問題です。もう 1 つは、誤検知を回避することです。つまり、結果が i でない場合に i を生成しないようにすることです。実際の結果はそうではありません。計算結果を [ie, i+1-e) に入れるかもしれません。) ) 数学的な証明を使用して、最終的なエラーの境界を決定します。ある誤差範囲 e について、正確な数学的結果が整数 i である場合、実際に計算された結果は半開区間 [つまり、i+1-e) にあることを証明できれば、次の式を作成できます。計算結果に e を加算し、その計算結果を切り捨てて整数にすることにより、正確な数学的結果を得ることができます。(簡潔にするために、特定の複雑さを省略しました。1 つは、e を追加すると i+1 に切り上げられる可能性があるという問題です。もう 1 つは、誤検知を回避することです。つまり、結果が i でない場合に i を生成しないようにすることです。実際の結果はそうではありません。計算結果を [ie, i+1-e) に入れるかもしれません。) 正確な数学的結果が整数 i である場合、実際に計算された結果は半開区間 [つまり、i+1-e) にある場合、計算結果に e を追加して結果を切り捨てることにより、正確な数学的結果を生成できます。その計算を整数にします。(簡潔にするために、特定の複雑さを省略しました。1 つは、e を追加すると i+1 に切り上げられる可能性があるという問題です。もう 1 つは、誤検知を回避することです。つまり、結果が i でない場合に i を生成しないようにすることです。実際の結果はそうではありません。計算結果を [ie, i+1-e) に入れるかもしれません。) 正確な数学的結果が整数 i である場合、実際に計算された結果は半開区間 [つまり、i+1-e) にある場合、計算結果に e を追加して結果を切り捨てることにより、正確な数学的結果を生成できます。その計算を整数にします。(簡潔にするために、特定の複雑さを省略しました。1 つは、e を追加すると i+1 に切り上げられる可能性があるという問題です。もう 1 つは、誤検知を回避することです。つまり、結果が i でない場合に i を生成しないようにすることです。実際の結果はそうではありません。計算結果を [ie, i+1-e) に入れるかもしれません。)

ご覧のとおり、「正しい」方法は一般的に非常に困難です。複雑なコードの場合、標準の数学ライブラリ関数 (正弦、対数など) を計算するための高品質のライブラリ ルーチンの設計など、限られた高価値の状況でのみ証明が生成されます。

単純なコードの場合、証明は単純かもしれません。答えが正確に整数であることがわかっていて、誤差が .5 ほど大きくならないほど多くの浮動小数点演算を行っていないことがわかっている場合、正しい答えを生成する正しい方法は、単純に を足すことです。 5 切り捨てます。これは正しいことが証明されているため、何も問題はありません。(実際には、実行する操作の数だけでなく、その性質も重要です。同様の大きさの値の減算は、相対誤差が大きいエラーを生成することで有名です。そのような結果に大きな大きさを掛けると、絶対値が大きくなる可能性があります。エラー。)

数学的に正しい答えが正確に整数であることがわからない場合、切り捨ては間違っています。計算の誤差の限界がわからない場合、切り捨ての前に修正を追加するのは間違っています。この問題に対する一般的な答えはありません。計算を理解する必要があります

于 2012-06-28T13:13:11.303 に答える