将双精度值四舍五入到(有点)较低的精度的好方法是什么?

What is a good way to round double-precision values to a (somewhat) lower precision?

提问人:MuldeR 提问时间:1/4/2013 最后编辑:MuldeR 更新时间:5/15/2019 访问量:4119

问:

我的问题是我必须使用第三方函数/算法,该函数/算法将精度值数组作为输入,但显然对输入数据中的非常小的变化很敏感。但是,对于我的应用程序,我必须为(几乎)相同的输入获得相同的结果!特别是,我有两个测试输入数组,它们在小数点后的第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 截断到小数点后最多 L-N 个位置,其中 L 是小数点后的位置数,精度可以存储(表示)给定值; N 是固定的, 喜欢 3.这意味着对于“小”值,我们允许在小数点后比“大”值有更多的位置。换句话说,我想将 64 位浮点值舍入为(某种程度上)较小的精度,如 60 位或 56 位,然后将其存储回 64 位双精度值。

这对你有意义吗?如果是这样,您能否提出一种方法来(有效地)C++ ???

提前致谢!

C++ 舍入 浮点精度

评论

0赞 1/4/2013
你想把它四舍五入以 10 为基数,还是以 2 为基数也可以?
0赞 MuldeR 1/4/2013
嗨,我认为 base-2 也可以,只要它适应输入。
0赞 MSalters 1/4/2013
这个想法从根本上是有缺陷的。所有数字都是“几乎相同的”,因为 1.00 几乎与 1.01 相同,1.01 与 1.02 几乎相同,等等。因此,如果和然后也和也f(1.00) == f(1.01)f(1.01)==f(1.02)f(1.00)==f(1.02)f(1.00)==f(1E7)
0赞 MuldeR 1/4/2013
女士,我明白你的意思。当然,实际上我所做的是将输入值“量化”到多个“箱”中,其中每个箱子都覆盖一定范围的输入值(对于“大”值,箱子变得“更宽”)。最后,我将该值替换为其 bin 的平均值。仍然可能发生两个值非常接近的情况,但一个值恰好是边界的“左边”,另一个值恰好是边界的“右边”。可能是我需要忍受的案例。或者你有更好的建议来解决这个问题吗?
0赞 Eric Postpischil 1/12/2013
舍入并不能解决您描述的问题。为了便于说明,我将使用十进制。假设您有两个期望相同的结果,即 10.22 和 10.24,并将它们四舍五入到三位数,得到 10.2 和 10.2 它们是相同的,一切正常。但是,如果结果是 10.24 和 10.26,则将它们四舍五入会产生 10.2 和 10.3,并且它们并不相同。在没有其他规范的情况下,舍入不会使接近但不相同的结果相同,例如所有结果都靠近舍入区间的中心,而不是边界附近。

答:

1赞 Xavier Holt 1/4/2013 #1

如果你看一下双位布局,你可以看到如何将其与一点按位魔术相结合,以实现快速(二进制)舍入到任意精度。您有以下位布局:

SEEEEEEEEEEEFFFFFFFFFFF.......FFFFFFFFFF

其中是符号位,S 是指数位,S 是分数位。您可以像这样制作位掩码:SEF

11111111111111111111111.......1111000000

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

SEEEEEEEEEEEFFFFFFFFFFF.......FFFF000000

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

希望对您有所帮助!

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

评论

0赞 MuldeR 1/4/2013
谢谢,泽维尔。这可能是一个解决方案。但是我究竟如何在 C++ 代码中执行该位操作呢?将“double”值转换为“unsigned char*”指针?我需要关心字节序吗?
0赞 Xavier Holt 1/4/2013
@MuldeR - 这可能是最好的方法。我环顾四周,没有什么特别方便的......还有“联合黑客”——这里的人们喜欢指出这是技术上未定义的行为,但它应该适用于每个主流编译器。
0赞 Joe 6/7/2013
进行强制转换的唯一合法方法是从双精度到整数(并返回)进行 memcpy。类型双关语和联合黑客都是未定义的行为。memcpy 版本可以包装到一个byte_cast<>模板中
1赞 MuldeR 1/4/2013 #2

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

但是,经过更多搜索,我遇到了 frexp() 和 ldexp() 函数!这些函数使我能够访问给定双精度值的“尾数”和“指数”,还可以从尾数 + 指数转换回双精度值。现在我只需要把尾数弄圆。

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 值......

有什么意见或建议吗?

评论

0赞 Paul Chernoch 1/12/2013
我需要完全相同的东西,也在 base 2 中,但在 C# 中。希望我能找到与 C++ 类似的在 C# 中摆弄双精度位的方法。
1赞 abygm 6/7/2013 #3

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

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

0赞 lukasl1991 5/15/2019 #4

我知道这个问题已经很老了,但我也在寻找一种将值舍入到较低精度的方法。也许,这个答案对那里的某人有所帮助。double

想象一下二进制表示中的浮点数。例如。位表示数字的整数部分,从左到右用 、、 加权。小数部分的位用 、 、 加权,等于 、 、 。1101.10111012^32^22^12^01012^-12^-22^-31/21/41/8

那么,当你在小数点后切掉你的数字二位时,你会产生什么十进制误差呢?在本例中,由于设置了位。如果未设置该位,则错误为 。所以,错误是 .0.1250<= 0.125

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

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

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

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

一般来说,尾数由整数和小数部分组成。我们称它们的长度为 和 .正指数描述。此外,成立。上述不等式的解变为:因为在你的分数部分结束后,你必须停止切断尾数!( 是此处的十进制精度。ifi52=f+i52-i-log2(10^n) >= xn

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

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

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

如果大于 ,请记住只移动 。否则,你会影响你的尾数的组成部分。xff