C++ 在c+中使用位掩码消除浮点错误+;

C++ 在c+中使用位掩码消除浮点错误+;,c++,floating-point,C++,Floating Point,假设我有一个问题,我想计算大量的双精度,每次我计算一个新的双精度,我想看看我以前是否见过这个双精度。最好(最快)的方法是什么 如果double是精确的,我将创建一个集并检查成员身份是否为O(logn),或者是一个带有O(1)的散列集。但它们并不精确,因此我需要遍历所有之前看到的双精度,并检查它们是否在新计算的双精度的公差范围内,这是缓慢的:O(n)。我的想法是只保留双精度的前30位,在集合中给出2^-30的精度,并检查它们是否相等。这是个好主意还是有更好的办法?如何只保留一个双精度的30个最高有

假设我有一个问题,我想计算大量的双精度,每次我计算一个新的双精度,我想看看我以前是否见过这个双精度。最好(最快)的方法是什么


如果double是精确的,我将创建一个
并检查成员身份是否为O(logn),或者是一个带有O(1)的散列集。但它们并不精确,因此我需要遍历所有之前看到的双精度,并检查它们是否在新计算的双精度的公差范围内,这是缓慢的:O(n)。我的想法是只保留双精度的前30位,在集合中给出2^-30的精度,并检查它们是否相等。这是个好主意还是有更好的办法?如何只保留一个双精度的30个最高有效位?

无论采用何种舍入模式,您都会画一条线,落在线的一侧或另一侧的数字可以任意接近(浮动时为1 ulp)

因此,您需要使用至少两个具有不同舍入的集合,并检查具有一个舍入模式或另一个舍入模式的浮点值是否在集合或另一个中

例如,对于公差
t
,这类似于:

is_close = ( round(x,t) is in set1) or ( round(x+t/2,t) is in set2 )
set1 add round(x,t)
set2 add round(x+t/2,t)
我建议使用地板或天花板作为规则性的截断模式

注1:
n*t+t-eps
n*t-t+eps
对于整数
n
和小
0
,将被视为接近,距离接近
2t
,因此在上述公式中,t应选择为半公差


注2:这是一个绝对公差。如果目的是一个相对公差(截断有效位的位-这会降低精度),那么公式可能会更复杂一些,用于处理二进制边界,但同样的算法应该适用(只是t将是浮动的…)

如果需要确定之前是否在某个范围内看到给定值(
a
b
),则可以使用所有之前看到的值的
std::set
。我们可以使用
上限()

// untested
bool have_seen(const std::set<double>& past, double a, double b)
{
    auto it = past.upper_bound(a);
    return it != past.end() && *it < b;
}
//未经测试
布尔已经看到(常数标准::设置和过去,双a,双b)
{
自动it=过去的上限(a);
返回它!=pass.end();
}

(使用
下限
如果您确实需要降低double的精度(目前还不清楚这是否是您的特定问题的解决方案),那么如果您的系统使用通用标准二进制格式就不太难了

我们可以利用最低有效数字包含尾数的事实,只需将这些数字的正确数字设置为零:

#include <cstdint>
#include <limits>

double trunc(double d, int precision) noexcept
{
    // This function requires IEEE-754 binary representations
    static_assert(std::numeric_limits<double>::is_iec559);
    static_assert(std::numeric_limits<double>::radix == 2);
    using Integer = std::uint_fast32_t;

    static const auto max_precision = std::numeric_limits<double>::digits;
    if (precision < 1) {
        // invalid
        return 0;
    }
    if (precision >= max_precision) {
        // no-op
        return d;
    }

    auto& i = reinterpret_cast<Integer&>(d);
    static_assert(sizeof i >= sizeof d);
    auto mask = ~Integer{} << (max_precision - precision);
    i &= mask;

    return d;
}
很难说这是否是您真正需要的。如果您想将结果收集到直方图桶中,这可能很有用:

#include <iostream>
#include <iomanip>
#include <map>
#include <random>
#include <string>

int main()
{
    std::mt19937 gen(std::random_device{}());
    std::normal_distribution<double> dist(0.5,0.1);

    std::map<double,std::size_t> histogram;
    for (int i = 0;  i < 10000;  ++i) {
        auto d = trunc(dist(gen), 5);
        ++histogram[d];
    }

    for (auto const& [value, freq]: histogram) {
        std::cout << std::fixed << std::setprecision(3) << std::setw(5)
                  << value << ": " << std::string(freq/50, '*') << '\n';
    }
}

虽然这个正态分布看起来是歪斜的,但这只是因为当指数增加时,桶大小在0.5处变化。

这里有一种方法,即使您无法在RAM中保存所有的双精度数据,它也会起作用

  • 收集替身名单。O(N)
  • 对它们进行排序。O(N*logN)
  • 扫描list.Foreach项,根据它与最后一个输出值的接近程度,输出或丢弃它。O(n)
  • 如果“测试‘关闭’”的成本很高,请注意,此算法只执行了N次测试。一些建议的算法是O(N*M),其中N是输入计数,M是输出计数


    进一步注意,输出是不确定的。我的意思是,重新排列输入列表可能会改变保留哪些双精度和丢弃哪些双精度。这一警告可能适用于任何和所有算法。

    一开始,为什么不保留两个集合/数组,第一个是原始计算值,第二个是四舍五入值。因此,在第二个数组中检查数字,并且当从第一个数组中需要时,仍然有“满双”。“它们不精确”:这是错误的模型。在C++模型中,如IEEE-75中的每个浮点对象都表示一个数字(或是一个数字)。。对它们的运算近似于实数运算。这种差异对于分析、设计、证明和调试浮点计算至关重要。Re“这是个好主意还是有更好的方法?”:不,这不是一个好方法。对浮点数进行分区的任何尝试都必然会放大某些错误或破坏传递性(无法分区)。您可能应该备份并解释为什么要检查以前是否看到过一个
    double
    。可能有更好的方法来解决导致您这样做的问题,而不是解决试图查找以前是否看到过附近的
    double
    的问题。@user3124812-一个“舍入值”可能在其范围的高端;另一个可能在下一个范围,但在低端。这些可能来自“足够接近”以“匹配”的原始值。当你试图解决这个难题时,你需要解决一个非确定性的问题。啊哈,我没有想到这一点,当然总会有一个边界。我想我会尝试使用一个无序集和一个自定义谓词,因为这似乎适合我的需要(我想)。是的,一些基于散列的容器用于获取O(1),而不是基于O(logn)中的b-tree。
    #include <iostream>
    #include <iomanip>
    #include <map>
    #include <random>
    #include <string>
    
    int main()
    {
        std::mt19937 gen(std::random_device{}());
        std::normal_distribution<double> dist(0.5,0.1);
    
        std::map<double,std::size_t> histogram;
        for (int i = 0;  i < 10000;  ++i) {
            auto d = trunc(dist(gen), 5);
            ++histogram[d];
        }
    
        for (auto const& [value, freq]: histogram) {
            std::cout << std::fixed << std::setprecision(3) << std::setw(5)
                      << value << ": " << std::string(freq/50, '*') << '\n';
        }
    }