C++、クラス、および関連するものを自分で学習するために、できるだけ完全な Fraction クラスを作成しようとしています。とりわけ、浮動小数点の例外とオーバーフローに対してある程度の「保護」を確保したかったのです。
目的:
一般的な演算で見られる算術演算でのオーバーフローと浮動小数点の例外を回避し、消費する時間/メモリを最小限に抑えます。回避できない場合は、少なくとも検出します。
また、アイデアは、より大きな型にキャストしないことです。これにより、いくつかの問題が発生します(より大きなタイプがない可能性があるなど)
私が見つけたケース:
+、-、*、/、pow、root のオーバーフロー
操作はほとんど簡単です (
a
とb
ですLong
):- a+b: LONG_MAX - b > a の場合、オーバーフローが発生します。(不十分。
a
またはb
マイナスになる可能性があります) - ab: LONG_MAX - a > -b の場合、オーバーフローが発生します。(同上)
- a*b: LONG_MAX / b > a の場合、オーバーフローが発生します。(b != 0 の場合)
- a/b: a << b の場合は SIGFPE をスローするか、b << 0 の場合はオーバーフローをスローする可能性があります
- pow(a,b): (pow(LONG_MAX, 1.0/b) > a の場合、オーバーフローがあります。
- pow(a,1.0/b): a/b と同様
- a+b: LONG_MAX - b > a の場合、オーバーフローが発生します。(不十分。
x = LONG_MIN (または同等) の場合の abs(x) のオーバーフロー
これは面白いです。すべての符号付き型には、可能な値の範囲 [-x-1,x] があります。abs(-x-1) = x+1 = -x-1 オーバーフローのため。これは、abs(x) < 0 の場合があることを意味します。
- -1 で割った大きな数値の SIGFPE
numerator/gcd(numerator,denominator) を適用すると見つかります。ときどき gcd が -1 を返し、浮動小数点例外が発生しました。
簡単な修正:
- 一部の操作では、オーバーフローを簡単に確認できます。その場合は、いつでも double にキャストできます (大きな整数で精度が失われるリスクがあります)。アイデアは、キャストせずに、より良い解決策を見つけることです。
分数算術では、簡単化のために追加のチェックを行うことができる場合があります。a/b * c/d (互いに素) を解くために、最初に a/d と c/b を互いに素にすることができます。
または<0 または > 0 かどうそのオーバーフローを回避する関数 neg() を作成できますa
かを尋ねる場合、私はいつでもカスケードを行うことができます。b
そのひどい選択に加えて、T neg(T x){if (x > 0) return -x; else return x;},
- abs(x) の gcd と同様の状況 (どこでも x > LONG_MIN) を取ることができます
2. と 3. が最適な解決策かどうかはわかりませんが、十分だと思われます。私はそれらをここに投稿しているので、誰かがより良い答えを持っているかもしれません.
最も醜い修正
ほとんどの操作では、オーバーフローをチェックして回避するために、多くの追加操作を行う必要があります。これで、1つか2つのことを学ぶことができると確信しています。
例:
Fraction Fraction::operator+(Fraction f){
double lcm = max(den,f.den);
lcm /= gcd(den, f.den);
lcm *= min(den,f.den);
// a/c + b/d = [a*(lcm/d) + b*(lcm/c)] / lcm //use to create normal fractions
// a/c + b/d = [a/lcm * (lcm/c)] + [b/lcm * (lcm/d)] //use to create fractions through double
double p = (double)num;
p *= lcm / (double)den;
double q = (double)f.num;
q *= lcm / (double)f.den;
if(lcm >= LONG_MAX || (p + q) >= LONG_MAX || (p + q) <= LONG_MIN){
//cerr << "Aproximating " << num << "/" << den << " + " << f.num << "/" << f.den << endl;
p = (double)num / lcm;
p *= lcm / (double)den;
q = (double)f.num / lcm;
q *= lcm / (double)f.den;
return Fraction(p + q);
}
else
return normal(p + q, (long)lcm);
}
これらの算術演算でオーバーフローを回避する最善の方法はどれですか?
編集: このサイトには非常によく似た質問がいくつかありますが、それらは同じではありません (特定の関連のない状況では、回避する代わりに検出する、署名する代わりに署名しない、SIGFPE)。
それらすべてをチェックすると、次のような適切な回答を提供するために修正時に役立つ可能性のあるいくつかの回答が見つかりました。
- 符号なし加算でオーバーフローを検出します (私の場合ではなく、符号付きで作業しています):
uint32_t x, y; uint32_t value = x + y; bool overflow = value < x; // Alternatively "value < y" should also work
符号付き演算のオーバーフローを検出します。これは、分岐が多く、少し一般的すぎる可能性があり、オーバーフローを回避する方法については説明していません。
回答で言及されているCERTルールは良い出発点ですが、ここでも検出方法についてのみ説明します。
他の回答は一般的すぎるため、私が見ているケースにより具体的な回答があるかどうか疑問に思います。