gpt4 book ai didi

c# - 将 double 四舍五入到以位数给出的较低精度的有效方法

转载 作者:太空狗 更新时间:2023-10-29 21:13:34 27 4
gpt4 key购买 nike

在 C# 中,我想将 double 舍入到较低的精度,以便我可以将它们存储在关联数组中不同大小的桶中。与通常的舍入不同,我想舍入到一些有效位。因此,大数字在绝对值上的变化比小数字大得多,但它们往往会按比例变化。因此,如果我想四舍五入到 10 个二进制数字,我会找到十个最高有效位,并将所有低位归零,可能会添加一个小数字以进行四舍五入。

我更喜欢将“中途”数字四舍五入。

如果是整数类型,可能的算法如下:

  1. Find: zero-based index of the most significant binary digit set H.
2. Compute: B = H - P,
where P is the number of significant digits of precision to round
and B is the binary digit to start rounding, where B = 0 is the ones place,
B = 1 is the twos place, etc.
3. Add: x = x + 2^B
This will force a carry if necessary (we round halfway values up).
4. Zero out: x = x mod 2^(B+1).
This clears the B place and all lower digits.

问题是找到一种有效的方法来找到最高位集。如果我使用的是整数,可以通过一些很酷的技巧来找到 MSB。如果可以的话,我不想调用 Round(Log2(x)) 。此函数将被调用数百万次。

注意:我已经阅读了这个 SO 问题:

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

它适用于 C++。我正在使用 C#。

更新:

这是我正在使用的代码(根据回答者提供的内容修改):

/// <summary>
/// Round numbers to a specified number of significant binary digits.
///
/// For example, to 3 places, numbers from zero to seven are unchanged, because they only require 3 binary digits,
/// but larger numbers lose precision:
///
/// 8 1000 => 1000 8
/// 9 1001 => 1010 10
/// 10 1010 => 1010 10
/// 11 1011 => 1100 12
/// 12 1100 => 1100 12
/// 13 1101 => 1110 14
/// 14 1110 => 1110 14
/// 15 1111 =>10000 16
/// 16 10000 =>10000 16
///
/// This is different from rounding in that we are specifying the place where rounding occurs as the distance to the right
/// in binary digits from the highest bit set, not the distance to the left from the zero bit.
/// </summary>
/// <param name="d">Number to be rounded.</param>
/// <param name="digits">Number of binary digits of precision to preserve. </param>
public static double AdjustPrecision(this double d, int digits)
{
// TODO: Not sure if this will work for both normalized and denormalized doubles. Needs more research.
var shift = 53 - digits; // IEEE 754 doubles have 53 bits of significand, but one bit is "implied" and not stored.
ulong significandMask = (0xffffffffffffffffUL >> shift) << shift;
var local_d = d;
unsafe
{
// double -> fixed point (sorta)
ulong toLong = *(ulong*)(&local_d);
// mask off your least-sig bits
var modLong = toLong & significandMask;
// fixed point -> float (sorta)
local_d = *(double*)(&modLong);
}
return local_d;
}

更新 2:Dekker 的算法

多亏了另一位受访者,我从 Dekker 的算法中得出了这一点。它舍入到最接近的值,而不是像上面的代码那样截断,并且它只使用安全代码:

private static double[] PowersOfTwoPlusOne;

static NumericalAlgorithms()
{
PowersOfTwoPlusOne = new double[54];
for (var i = 0; i < PowersOfTwoPlusOne.Length; i++)
{
if (i == 0)
PowersOfTwoPlusOne[i] = 1; // Special case.
else
{
long two_to_i_plus_one = (1L << i) + 1L;
PowersOfTwoPlusOne[i] = (double)two_to_i_plus_one;
}
}
}

public static double AdjustPrecisionSafely(this double d, int digits)
{
double t = d * PowersOfTwoPlusOne[53 - digits];
double adjusted = t - (t - d);
return adjusted;
}

更新 2:时间

我进行了测试,发现 Dekker 的算法比 TWICE 快两倍!

Number of calls in test: 100,000,000
Unsafe Time = 1.922 (sec)
Safe Time = 0.799 (sec)

最佳答案

Dekker 的算法会将 float 拆分为高位和低位部分。如果有效数中有 s 位(IEEE 754 64 位二进制中为 53),则 *x0 接收高 s-b 位,这是您请求的,*x1 接收剩余的位,您可以丢弃这些位。在下面的代码中,Scale 的值应为 2b。如果 b 在编译时已知,例如常量 43,您可以将 Scale 替换为 0x1p43。否则,您必须以某种方式生成 2b

这需要舍入到最近的模式。 IEEE 754 算术就足够了,但其他合理的算术也可能没问题。它将关系舍入为偶数,这不是您所要求的(向上关系)。有必要吗?

这假设 x * (Scale + 1) 没有溢出。必须以 double (不大于)评估操作。

void Split(double *x0, double *x1, double x)
{
double d = x * (Scale + 1);
double t = d - x;
*x0 = d - t;
*x1 = x - *x0;
}

关于c# - 将 double 四舍五入到以位数给出的较低精度的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14285492/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com