7

我的问题是我必须使用第三方函数/算法,它需要一个精度值数组作为输入,但显然对输入数据中非常小的变化很敏感。但是对于我的应用程序,我必须为(几乎)相同的输入获得相同的结果!特别是我有两个测试输入数组,它们在小数点后的第 5 位是相同的,但我仍然得到不同的结果。那么导致“问题”的原因肯定是在小数点后第5位之后。

现在我的想法是将输入四舍五入到稍低的精度,以便从非常相似但不是100% 相同的输入中获得相同的结果。因此,我正在寻找一种很好/有效的方法来将精度值舍入到略低的精度。到目前为止,我正在使用此代码四舍五入到小数点后的第 9 位:

double x = original_input();
x = double(qRound(x * 1000000000.0)) / 1000000000.0;

这里 qRound() 是来自 Qt 的普通双整数舍入函数。这段代码有效,它确实解决了我对两个“有问题的”测试集的问题。但是:有没有更有效的方法呢?

还有什么困扰我:对于在 -100.0 到 100.0 范围内的输入数据(与我当前的输入数据一样),四舍五入到小数点后的第 9 位可能是合理的。但是,例如,对于 -0.001 到 0.001 范围内的输入数据,它可能太多(即,精度损失太大)。不幸的是,我不知道在其他情况下我的输入值在什么范围内......

毕竟,我认为我需要的是类似于执行以下操作的函数:通过适当的舍入,将给定的精度值 X 截断到小数点后最多 LN 位置,其中 L 是位置数在精度可以存储(表示)给定值的小数点之后;并且 N 是固定的,例如 3。这意味着对于“小”值,我们将允许小数点后的位置比“大”值更多。换句话说,我想将 64 位浮点值舍入到(稍微)更小的精度,如 60 位或 56 位,然后将其存储回 64 位双精度值。

你能理解这个吗?如果是这样,你能建议一种在 C++ 中(有效地)做到这一点的方法吗???

提前致谢!

4

4 回答 4

1

感谢您到目前为止的输入。

然而,经过一番搜索,我发现了 frexp() 和 ldexp() 函数!这些函数让我可以访问给定double值的“尾数”和“指数”,还可以从尾数+指数转换回double。现在我只需要舍入尾数。

double value = original_input();
static const double FACTOR = 32.0;
int exponent;
double temp = double(round(frexp(value, &exponent) * FACTOR));
value = ldexp(temp / FACTOR, exponent);

我不知道这是否有效,但它给出了合理的结果:

0.000010000000000   0.000009765625000
0.000010100000000   0.000010375976563
0.000010200000000   0.000010375976563
0.000010300000000   0.000010375976563
0.000010400000000   0.000010375976563
0.000010500000000   0.000010375976563
0.000010600000000   0.000010375976563
0.000010700000000   0.000010986328125
0.000010800000000   0.000010986328125
0.000010900000000   0.000010986328125
0.000011000000000   0.000010986328125
0.000011100000000   0.000010986328125
0.000011200000000   0.000010986328125
0.000011300000000   0.000011596679688
0.000011400000000   0.000011596679688
0.000011500000000   0.000011596679688
0.000011600000000   0.000011596679688
0.000011700000000   0.000011596679688
0.000011800000000   0.000011596679688
0.000011900000000   0.000011596679688
0.000012000000000   0.000012207031250
0.000012100000000   0.000012207031250
0.000012200000000   0.000012207031250
0.000012300000000   0.000012207031250
0.000012400000000   0.000012207031250
0.000012500000000   0.000012207031250
0.000012600000000   0.000012817382813
0.000012700000000   0.000012817382813
0.000012800000000   0.000012817382813
0.000012900000000   0.000012817382813
0.000013000000000   0.000012817382813
0.000013100000000   0.000012817382813
0.000013200000000   0.000013427734375
0.000013300000000   0.000013427734375
0.000013400000000   0.000013427734375
0.000013500000000   0.000013427734375
0.000013600000000   0.000013427734375
0.000013700000000   0.000013427734375
0.000013800000000   0.000014038085938
0.000013900000000   0.000014038085938
0.000014000000000   0.000014038085938
0.000014100000000   0.000014038085938
0.000014200000000   0.000014038085938
0.000014300000000   0.000014038085938
0.000014400000000   0.000014648437500
0.000014500000000   0.000014648437500
0.000014600000000   0.000014648437500
0.000014700000000   0.000014648437500
0.000014800000000   0.000014648437500
0.000014900000000   0.000014648437500
0.000015000000000   0.000015258789063
0.000015100000000   0.000015258789063
0.000015200000000   0.000015258789063
0.000015300000000   0.000015869140625
0.000015400000000   0.000015869140625
0.000015500000000   0.000015869140625
0.000015600000000   0.000015869140625
0.000015700000000   0.000015869140625
0.000015800000000   0.000015869140625
0.000015900000000   0.000015869140625
0.000016000000000   0.000015869140625
0.000016100000000   0.000015869140625
0.000016200000000   0.000015869140625
0.000016300000000   0.000015869140625
0.000016400000000   0.000015869140625
0.000016500000000   0.000017089843750
0.000016600000000   0.000017089843750
0.000016700000000   0.000017089843750
0.000016800000000   0.000017089843750
0.000016900000000   0.000017089843750
0.000017000000000   0.000017089843750
0.000017100000000   0.000017089843750
0.000017200000000   0.000017089843750
0.000017300000000   0.000017089843750
0.000017400000000   0.000017089843750
0.000017500000000   0.000017089843750
0.000017600000000   0.000017089843750
0.000017700000000   0.000017089843750
0.000017800000000   0.000018310546875
0.000017900000000   0.000018310546875
0.000018000000000   0.000018310546875
0.000018100000000   0.000018310546875
0.000018200000000   0.000018310546875
0.000018300000000   0.000018310546875
0.000018400000000   0.000018310546875
0.000018500000000   0.000018310546875
0.000018600000000   0.000018310546875
0.000018700000000   0.000018310546875
0.000018800000000   0.000018310546875
0.000018900000000   0.000018310546875
0.000019000000000   0.000019531250000
0.000019100000000   0.000019531250000
0.000019200000000   0.000019531250000
0.000019300000000   0.000019531250000
0.000019400000000   0.000019531250000
0.000019500000000   0.000019531250000
0.000019600000000   0.000019531250000
0.000019700000000   0.000019531250000
0.000019800000000   0.000019531250000
0.000019900000000   0.000019531250000
0.000020000000000   0.000019531250000
0.000020100000000   0.000019531250000

毕竟似乎喜欢我一直在寻找的东西:

http://img833.imageshack.us/img833/9055/clipboard09.png

现在我只需要为我的功能找到好的 FACTOR 值....

有什么意见或建议吗?

于 2013-01-04T14:42:15.860 回答
1

从问题中看不出业务场景;我仍然觉得您正在尝试查看这些值是否在可接受的范围内。而不是 ==,您可以检查第二个值是否在某个百分比范围内(例如 +/- 0.001%)

如果范围百分比不能固定(平均值,根据精度长度而变化;例如,对于小数点后 2 位,0.001% 很好,但对于小数点后 4 位,需要 0.000001%),那么您可以通过 1/尾数得出。

于 2013-06-07T10:32:43.897 回答
1

如果你看一下双位布局,你会看到如何将它与一些位魔法结合起来以实现快速(二进制)舍入到任意精度。您有以下位布局:

SEEEEEEEEEEEFFFFFFFFFFF.......FFFFFFFFFF

其中S是符号位,Es 是指数位,Fs 是小数位。你可以像这样制作一个位掩码:

11111111111111111111111.......1111000000

并按位和 ( &) 将两者结合在一起。结果是原始输入的四舍五入版本:

SEEEEEEEEEEEFFFFFFFFFFF.......FFFF000000

您可以通过更改尾随零的数量来控制截断多少数据。更多的零 = 更多的舍入;更少=更少。您还可以获得您想要的其他效果:小输入值的影响比例小于大输入值,因为每个位对应的“位置”由指数决定。

希望有帮助!

警告: 这在技术上是截断而不是真正的舍入(所有值都将变得更接近于零,无论它们与其他可能的结果有多接近),但希望它对您的情况同样有用。

于 2013-01-04T02:36:29.327 回答
0

我知道这个问题已经很老了,但我也搜索了一种将double值舍入到较低精度的方法。也许,这个答案可以帮助那里的人。

想象一个二进制表示的浮点数。例如1101.101. 这些位1101表示数字的整数部分,并用2^3, 2^2, 2^1,2^0从左到右加权。小数部分的位1012^-1, 2^-2,加权2^-3,等于1/2, 1/4, 1/8

那么,当您将小数点后的两位数截去时,您会产生什么小数错误?在0.125此示例中,由于已设置该位。如果未设置该位,则错误为0。所以,错误是<= 0.125

现在以更一般的方式思考:如果您有一个无限长的尾数,则小数部分会收敛到 1(请参见此处)。事实上,你只有 52 位(见这里),所以总和是“几乎”1。所以切断所有小数位会导致错误,<= 1这并不奇怪!(请记住,您的整数部分也占用尾数空间!但如果您假设一个像二进制表示的数字1.51.1您的尾数只存储小数点后的部分。)

由于截断所有小数位会导致 的错误<= 1,因此截断除小数点右边的第一位以外的所有位都会导致错误,<= 1/2因为该位是用 加权的2^-1。再保留一点会将您的错误减少到<= 1/4.

这可以通过一个函数来描述,f(x) = 1/2^(52-x)其中x是从右侧计算的截止位数,并且y = f(x)是结果错误的上限。

小数点后两位四舍五入意味着按常见的百分之一“分组”数字。这可以通过上面的函数来完成:1/100 >= 1/2^(52-x). 这意味着在切断 x 位时,您产生的错误以百分之一为界。用 x 求解这个不等式得到:52-log2(100) >= x其中52-log2(100)45.36。这意味着切断不超过45位可确保浮点后两个小数(!)位置的“精度”。

通常,您的尾数由整数部分和小数部分组成。让我们称它们的长度if。正指数描述i。而且52=f+i持有。上述不等式的解变为:52-i-log2(10^n) >= x因为你的小数部分结束后,你必须停止截断尾数!(n这里是小数精度。)

应用对数规则,您可以计算允许截断的最大位数,如下所示:

x = f - (uint16_t) ceil(n / 0.3010299956639812);其中常数表示log10(2)。然后可以使用以下方法进行截断:

mantissa >>= x; mantissa <<= x;

如果x大于f,请记住仅移位f。否则,您将影响尾数的组成部分。

于 2019-05-15T11:16:13.617 回答