0 から 1 の間の NSNumber があり、X/Y を使用して表すことができるとします。この場合、X と Y をどのように計算すればよいでしょうか。比較したくない:
if (number.doubleValue == 0.125)
{
X = 1;
Y = 8;
}
だから私は0.125の1/8を取得します
0 から 1 の間の NSNumber があり、X/Y を使用して表すことができるとします。この場合、X と Y をどのように計算すればよいでしょうか。比較したくない:
if (number.doubleValue == 0.125)
{
X = 1;
Y = 8;
}
だから私は0.125の1/8を取得します
それは比較的簡単です。たとえば、0.375
は と同等0.375/1
です。
最初のステップは、分子が整数値(a)になるまで分子と分母を乗算して、 を与えることです375/1000
。
次に、最大公約数を見つけて、分子と分母の両方をそれで割ります。
GCD の (再帰的) 関数は次のとおりです。
int gcd (int a, int b) {
return (b == 0) ? a : gcd (b, a%b);
}
375
それをandで呼び出す1000
と吐き出される125
ので、分子と分母をそれで割ると となります3/8
。
(a)コメントで指摘されているように、整数型 (32 ビット整数の IEEE754 double など) よりも精度の高いビットを持つ数値には問題がある可能性があります。これを解決するには、範囲の広い整数 (long、または MPIR のような bignum ライブラリ) を選択するか、「十分に近い」戦略を選択します (整数部分に比べて小数部分が比較的重要でない場合は整数と見なします)。
もう 1 つの問題は、悪名高い0.1
や0.3
.
が利用可能な精度 ( など) によって制限される値の合計として数値を表すことができない限り、期待できる最善の方法は概算です。2-n
n
0.375
1/4 + 1/8
例として、単精度を考えてみましょう (理由は以下で説明します。私は怠惰すぎて 64 ビット全体を処理できません) 1/3
。単精度値として、これは次のように格納されます。
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
0 01111101 01010101010101010101010
この例では、符号は0
正の数です。
指数ビットは 125 を与え、127 バイアスを引くと -2 になります。したがって、乗数は、またはになります。2-2
0.25
仮数ビットは少しトリッキーです。これらは、1 から 23 (左から右) であるビットのすべての値1
と共に明示的な値の合計を形成します。したがって、仮数は次のように計算されます。2-n
1
n
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
0 01111101 01010101010101010101010
| | | | | | | | | | |
| | | | | | | | | | +-- 0.0000002384185791015625
| | | | | | | | | +---- 0.00000095367431640625
| | | | | | | | +------ 0.000003814697265625
| | | | | | | +-------- 0.0000152587890625
| | | | | | +---------- 0.00006103515625
| | | | | +------------ 0.000244140625
| | | | +-------------- 0.0009765625
| | | +---------------- 0.00390625
| | +------------------ 0.015625
| +-------------------- 0.0625
+---------------------- 0.25
Implicit 1
========================
1.3333332538604736328125
0.25
これに(前述の指数を参照)を掛けると、次のようになります。
0.333333313465118408203125
これが、 10 進数で約 7 桁の精度 (IEEE754 の倍精度では 15 桁) しか得られないと言われている理由です。
上記のアルゴリズムを介して実際の数値を渡すと、 は得られず1/3
、代わりに次のようになります。
5,592,405
---------- (or 0.333333313465118408203125)
16,777,216
しかし、それはアルゴリズム自体の問題ではなく、表現できる数の制限です。
計算を手伝ってくれたWolfram Alphaに感謝します。電卓に負荷をかける計算をする必要がある場合、それはその仕事に最適なツールの 1 つです。
余談ですが、仮数ビットが特定のパターンに従っていることに間違いなく気付くでしょう
0101010101...
。これは1/3
、無限に繰り返される 2 進値と無限に繰り返される 10 進値であるためです。01
正確に正確に表すには、最後に無限のビット数が必要です1/3
。
これを試すことができます:
- (CGPoint)yourXAndYValuesWithANumber:(NSNumber *)number
{
float x = 1.0f;
float y = x/number.doubleValue;
for(int i = 1; TRUE; i++)
{
if((float)(int)(y * i) == y * i)
// Alternatively floor(y * i), instead of (float)(int)(y * i)
{
x *= i;
y *= i;
break;
}
}
/* Also alternatively
int coefficient = 1;
while(floor(y * coefficient) != y * coefficient)coefficient++;
x *= coefficient, y *= coefficient;*/
return CGPointMake(x, y);
}
無効な入力がある場合、これは機能しません。X と Y は存在し、有効な自然数 (1 から無限大) でなければなりません。それを破る良い例は 1/pi です。制限がある場合は、それらを実装するために批判的思考を行うことができます。