我正在寻找一种方法,将浮点数的确切值转换为两个整数的有理商,即
等等。这里有很多关于截断实数表示为浮点数的最佳有理逼近的问题/答案。然而,我对浮点数的确切值感兴趣,它本身是一个具有不同表示的有理数。更具体地说,浮点数的数学集合是有理数的子集。在IEEE 754二进制浮点标准的情况下,它是二进制有理数的子集。无论如何,任何浮点数都可以转换为两个有限精度整数的有理商
例如,假设IEEE 754单精度二进制浮点格式,
根据我的经验,在这里遍历(通过二分查找)Stern-Brocot树并不有用,因为那更适合近似浮点数的值,当它被解释为截断实数而不是精确的有理数时。
可能,连分数是正确的方法。
另一个问题是整数溢出。考虑将有理数表示为两个
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];
}
Fraction.limit_denominator
感兴趣,它恰好可以做到这一点。当然,这并不能解决C中的溢出问题,但该算法可能会有用。具体而言,在Python中,对于一个(Python)float x
,Fraction(x).limit_denominator(b_max)
可以得到你想要的结果。 - Mark Dickinsonboost::multiprecision
),计算的精度可以得到扩展(以内存和运行时间为代价)。 - Red.WaveFLT_RADIX
,它定义了浮点格式所使用的基数。乘以或除以FLT_RADIX
应该是精确的(请注意scalbn
函数),因此,给定一些不是整数的浮点值x
,可以通过将其乘以FLT_RADIX
直到结果为整数来找到所需的分子,并且分母是FLT_RADIX
的幂次方。 - Eric PostpischilFLT_RADIX
直到一个数字成为整数,不会在任何正常格式中溢出。理论上,可以定义一种浮点格式,使指数限制在奇怪的范围内,如-20到-10,而不是更正常的-126到127,但实际上,指数范围允许有效数字被缩放为整数。 - Eric Postpischil