アルゴリズム
目標数と分数r
のセットを考えてみましょう。最小の分数 をとします。F
{1/2, 1/4, ... 1/(2^N)}
1/(2^N)
P
次に、最適な合計は次のようになります。
S = P * round(r/P)
つまり、最適な合計S
は、利用可能な最小の分数 の整数P
倍になります。最大誤差err = r - S
は です± 1/2 * 1/(2^N)
。1/(2^N)
セット内の最小数であるよりも小さい数を使用する必要があるため、これ以上の解決策はありませんF
。
分数F
はすべて の 2 乗の倍数であるP = 1/(2^N)
ため、 の任意の整数倍はP
の分数の和として表すことができますF
。使用する必要がある分数のリストを取得するには、整数を 2 進数でエンコードし、2 進数の場所で "分数をソリューションに含める" としてround(r/P)
読み取ります。1
kth
kth
例:
r = 0.3
とF
をとり{1/2, 1/4, 1/8, 1/16, 1/32}
ます。
問題全体に 32 を掛けます。
r = 9.6
、およびF
として{16, 8, 4, 2, 1}
。
r
最も近い整数に丸めます。
取るr = 10
。
10
2 進整数 (5 桁) としてエンコードします
10 = 0b 0 1 0 1 0 ( 8 + 2 )
^ ^ ^ ^ ^
| | | | |
| | | | 1
| | | 2
| | 4
| 8
16
各バイナリ ビットを分数に関連付けます。
= 0b 0 1 0 1 0 ( 1/4 + 1/16 = 0.3125 )
^ ^ ^ ^ ^
| | | | |
| | | | 1/32
| | | 1/16
| | 1/8
| 1/4
1/2
証拠
2**N
すべての分数が整数になるように、関係するすべての数値を掛けて問題を変換することを検討してください。
元の問題:
r
範囲内のターゲット数0 < r < 1
と分数のリストを考えてみましょう{1/2, 1/4, .... 1/(2**N)
。S
合計するerror = r - S
と最小化される分数のリストのサブセットを見つけます。
次の同等の問題になります ( を掛けた後2**N
)。
r
範囲内のターゲット数と整数0 < r < 2**N
のリストを考えてみましょう。合計すると最小化される整数のリストのサブセットを見つけます。 {2**(N-1), 2**(N-2), ... , 4, 2, 1}
S
error = r - S
合計が特定の数になる 2 の累乗を選択する (可能な限りエラーが少ない) のは、単純に整数のバイナリ エンコードです。したがって、この問題は整数のバイナリ エンコーディングに帰着します。
- 解の存在: 任意の正の浮動小数点数
r
、0 < r < 2**N
は、整数にキャストしてバイナリ形式で表すことができます。
- 最適性: 解の整数バージョンの最大誤差は、の丸め誤差です
±0.5
。(元の問題では、最大誤差は±0.5 * 1/2**N
です。)
- 一意性: 任意の正の (浮動小数点) 数値には、一意の整数表現があり、したがって一意のバイナリ表現があります。(
0.5
= の例外の可能性があります。以下を参照してください。)
実装 (Python)
この関数は、問題を等価の整数に変換し、整数に丸めr
、次に のバイナリ表現をr
整数として読み取り、必要な分数を取得します。
def conv_frac (r,N):
# Convert to equivalent integer problem.
R = r * 2**N
S = int(round(R))
# Convert integer S to N-bit binary representation (i.e. a character string
# of 1's and 0's.) Note use of [2:] to trim leading '0b' and zfill() to
# zero-pad to required length.
bin_S = bin(S)[2:].zfill(N)
nums = list()
for index, bit in enumerate(bin_S):
k = index + 1
if bit == '1':
print "%i : 1/%i or %f" % (index, 2**k, 1.0/(2**k))
nums.append(1.0/(2**k))
S = sum(nums)
e = r - S
print """
Original number `r` : %f
Number of fractions `N` : %i (smallest fraction 1/%i)
Sum of fractions `S` : %f
Error `e` : %f
""" % (r,N,2**N,S,e)
出力例:
>>> conv_frac(0.3141,10)
1 : 1/4 or 0.250000
3 : 1/16 or 0.062500
8 : 1/512 or 0.001953
Original number `r` : 0.314100
Number of fractions `N` : 10 (smallest fraction 1/1024)
Sum of fractions `S` : 0.314453
Error `e` : -0.000353
>>> conv_frac(0.30,5)
1 : 1/4 or 0.250000
3 : 1/16 or 0.062500
Original number `r` : 0.300000
Number of fractions `N` : 5 (smallest fraction 1/32)
Sum of fractions `S` : 0.312500
Error `e` : -0.012500
補遺:0.5
問題
r * 2**N
で終わる場合は0.5
、切り上げまたは切り捨てが可能です。つまり、分数の合計として 2 つの可能な表現があります。
元の問題ステートメントのように、小数を使用する表現 (つまり、2 進表現の最小1
ビット数) が必要な場合は、両方の丸めオプションを試して、より経済的な方を選択してください。