[英]Exact value of a floating-point number as a rational
我正在尋找一種方法將浮點數的精確值轉換為兩個整數的合理商,即a / b
,其中b
不大於指定的最大分母b_max
。 如果滿足條件b <= b_max
是不可能的,則結果回落到仍然滿足條件的最佳近似。
堅持,稍等。 這里有很多關於截斷實數的最合理逼近的問題/答案,它被表示為浮點數。 但是我對浮點數的確切值感興趣,浮點數本身就是一個具有不同表示的有理數。 更具體地,浮點數的數學集合是有理數的子集。 在IEEE 754二進制浮點標准的情況下,它是二元有理數的子集。 無論如何,任何浮點數都可以轉換為兩個有限精度整數的有理商作為a / b
。
因此,例如假設IEEE 754單精度二進制浮點格式, float f = 1.0f / 3.0f
的有理等價不是1 / 3
,而是11184811 / 33554432
。 這是f
的精確值,它是IEEE 754單精度二進制浮點數的數學集合中的數字。
根據我的經驗,遍歷(通過二分搜索) Stern-Brocot樹在這里沒有用,因為當它被解釋為截斷的實數而不是精確數時,它更適合於近似浮點數的值。 理性的 。
可能, 繼續分數是要走的路。
這里的另一個問題是整數溢出。 想想我們想要將理性表示為兩個int32_t
的商,其中最大分母b_max = INT32_MAX
。 我們不能依賴像b > b_max
這樣的停止標准。 所以算法必須永遠不會溢出,否則它必須檢測溢出。
到目前為止我發現的是Rosetta Code的算法 ,該算法基於連續分數,但其來源提到它“仍然不完全”。 一些基本測試給出了很好的結果,但我無法確認其整體正確性,我認為它很容易溢出。
// https://rosettacode.org/wiki/Convert_decimal_number_to_rational#C
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
/* f : number to convert.
* num, denom: returned parts of the rational.
* md: max denominator value. Note that machine floating point number
* has a finite resolution (10e-16 ish for 64 bit double), so specifying
* a "best match with minimal error" is often wrong, because one can
* always just retrieve the significand and return that divided by
* 2**52, which is in a sense accurate, but generally not very useful:
* 1.0/7.0 would be "2573485501354569/18014398509481984", for example.
*/
void rat_approx(double f, int64_t md, int64_t *num, int64_t *denom)
{
/* a: continued fraction coefficients. */
int64_t a, h[3] = { 0, 1, 0 }, k[3] = { 1, 0, 0 };
int64_t x, d, n = 1;
int i, neg = 0;
if (md <= 1) { *denom = 1; *num = (int64_t) f; return; }
if (f < 0) { neg = 1; f = -f; }
while (f != floor(f)) { n <<= 1; f *= 2; }
d = f;
/* continued fraction and check denominator each step */
for (i = 0; i < 64; i++) {
a = n ? d / n : 0;
if (i && !a) break;
x = d; d = n; n = x % n;
x = a;
if (k[1] * a + k[0] >= md) {
x = (md - k[0]) / k[1];
if (x * 2 >= a || k[1] >= md)
i = 65;
else
break;
}
h[2] = x * h[1] + h[0]; h[0] = h[1]; h[1] = h[2];
k[2] = x * k[1] + k[0]; k[0] = k[1]; k[1] = k[2];
}
*denom = k[1];
*num = neg ? -h[1] : h[1];
}
所有有限double
都是有理數,如OP所述。
使用frexp()
將數字分解為其分數和指數。 由於范圍要求,最終結果仍然需要使用double
來表示整數值。 有些數字太小,( x
小於1.0/(2.0,DBL_MAX_EXP)
)和無窮大,非數字是問題。
frexp
函數將一個浮點數分解為一個歸一化的分數和一個2的整數冪。... interval [1 / 2,1}或0 ...
C11§7.12.6.42/ 3
#include <math.h>
#include <float.h>
_Static_assert(FLT_RADIX == 2, "TBD code for non-binary FP");
// Return error flag
int split(double x, double *numerator, double *denominator) {
if (!isfinite(x)) {
*numerator = *denominator = 0.0;
if (x > 0.0) *numerator = 1.0;
if (x < 0.0) *numerator = -1.0;
return 1;
}
int bdigits = DBL_MANT_DIG;
int expo;
*denominator = 1.0;
*numerator = frexp(x, &expo) * pow(2.0, bdigits);
expo -= bdigits;
if (expo > 0) {
*numerator *= pow(2.0, expo);
}
else if (expo < 0) {
expo = -expo;
if (expo >= DBL_MAX_EXP-1) {
*numerator /= pow(2.0, expo - (DBL_MAX_EXP-1));
*denominator *= pow(2.0, DBL_MAX_EXP-1);
return fabs(*numerator) < 1.0;
} else {
*denominator *= pow(2.0, expo);
}
}
while (*numerator && fmod(*numerator,2) == 0 && fmod(*denominator,2) == 0) {
*numerator /= 2.0;
*denominator /= 2.0;
}
return 0;
}
void split_test(double x) {
double numerator, denominator;
int err = split(x, &numerator, &denominator);
printf("e:%d x:%24.17g n:%24.17g d:%24.17g q:%24.17g\n",
err, x, numerator, denominator, numerator/ denominator);
}
int main(void) {
volatile float third = 1.0f/3.0f;
split_test(third);
split_test(0.0);
split_test(0.5);
split_test(1.0);
split_test(2.0);
split_test(1.0/7);
split_test(DBL_TRUE_MIN);
split_test(DBL_MIN);
split_test(DBL_MAX);
return 0;
}
產量
e:0 x: 0.3333333432674408 n: 11184811 d: 33554432 q: 0.3333333432674408
e:0 x: 0 n: 0 d: 9007199254740992 q: 0
e:0 x: 1 n: 1 d: 1 q: 1
e:0 x: 0.5 n: 1 d: 2 q: 0.5
e:0 x: 1 n: 1 d: 1 q: 1
e:0 x: 2 n: 2 d: 1 q: 2
e:0 x: 0.14285714285714285 n: 2573485501354569 d: 18014398509481984 q: 0.14285714285714285
e:1 x: 4.9406564584124654e-324 n: 4.4408920985006262e-16 d: 8.9884656743115795e+307 q: 4.9406564584124654e-324
e:0 x: 2.2250738585072014e-308 n: 2 d: 8.9884656743115795e+307 q: 2.2250738585072014e-308
e:0 x: 1.7976931348623157e+308 n: 1.7976931348623157e+308 d: 1 q: 1.7976931348623157e+308
請b_max
考慮b_max
。
使用ldexp(1, expo)
@gammatester或exp2(expo)
@Bob__替換pow(2.0, expo)
ldexp(1, expo)
更方便的代碼
while (*numerator && fmod(*numerator,2) == 0 && fmod(*denominator,2) == 0)
也可以使用一些性能改進。 但首先,讓我們根據需要獲得功能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.