【问题标题】:Why is this function producing incorrect values? [duplicate]为什么这个函数会产生不正确的值? [复制]
【发布时间】:2018-11-07 12:58:38
【问题描述】:

我有一个简单的函数模板来计算容器的平均值:

template<typename T>
T array_average( std::vector<T>& values ) {
    if( std::is_arithmetic<T>::value ) {
        if( !values.empty() ) {
            if( values.size() == 1 ) {
                return values[0];
            } else { 
                return (static_cast<T>( std::accumulate( values.begin(), values.end(), 0 )  ) / static_cast<T>( values.size() ) );
            }
        } else {
            throw std::runtime_error( "Can not take average of an empty container" ); 
        }
    } else {
        throw std::runtime_error( "T is not of an arithmetic type" );
    }
}

我在上面的static_cast&lt;&gt;s 中添加了尝试将计算强制为所需的类型&lt;T&gt;

当我在 main 中使用 uint64_t 调用此函数时

std::vector<uint64_t> values{ 1,2,3,4,5,6,7,8,9,10,11,12 };
std::cout << array_average( values ) << '\n';

此代码确实会产生 MSVC 的编译器警告 C4244 可能由于转换而丢失数据,但它运行正常,这给了我预期的结果,并将 6 打印到控制台。这是正确的,因为实际值为 6.5,但由于整数除法中的截断,6 是正确的。

现在如果我用上面的函数代替:

std::vector<double> values { 2.0, 3.5, 4.5, 6.7, 8.9 };
std::cout << array_average( values2 ) << '\n';

这应该给我5.12 的结果,但它显示的是4.6。这也给了我与上面相同的编译器警告,但它运行时没有运行时错误(执行中断),但给了我不正确的结果。

我不确定我的函数中的错误在哪里。我不知道这是不是因为那个编译器警告,还是我设计函数本身的方式。


-编辑-

一位用户建议这可能是此Q/A 的副本,我无法反驳它回答或不回答我的问题的事实。在提出这个问题时;我不知道这个错误来自于对std::accumulate 本身的不当使用。我不确定它是否来自编译器警告,该警告与转换可能导致数据丢失有关,或者我是否将其转换错误,或者是否与我一般如何实现此功能有关。在提供链接之前,我已经接受了在此页面上找到的答案。我将保留此 Q/A 以供将来参考和读者!除此之外,我确实很欣赏提供的链接,因为它确实有助于了解错误在我的代码中的位置、错误是什么以及导致它的原因,以及除了此页面上接受的答案之外如何正确修复它。

【问题讨论】:

  • 它应该返回 5.12,而不是 6.4。
  • @jwimberley 你是对的;我必须将这些值添加到 windows calc 错误...它是 5.12 而不是 6.4。但该函数仍然产生不正确的值。
  • 使用不那么随意的测试用例——从容易判断错误的测试用例开始。 {1.5, 1.5} 足以暴露错误,如果您没有不必要的单元素输入特殊情况,{1.5} 就足够了。
  • @FrancisCugler 我知道它的用途。除非您知道绝大多数平均将是单元素容器,并且您已经测量了性能并确定它很重要,否则这是一个毫无意义的过早优化,只会增加复杂性。
  • 您的问题因重复而被关闭这一事实不是判断性的。关闭它的条件是“这里已经回答了这个问题”。确实,您无法猜测要寻找什么才能找到此答案。你问这个问题做得很好(这是一个很好的问题 IMO)。不过,在链接的副本中有您的问题的答案。祝你有美好的一天。

标签: c++ templates stdvector


【解决方案1】:

您的static_cast 放错地方了。您正在投射累积的结果,但让累积以初始项的类型运行(此处为0,即int)。所以改为这样做:

return std::accumulate( values.begin(), values.end(), static_cast<T>(0) ) / static_cast<T>( values.size() );

(注意4.6确实是static_cast&lt;double&gt;(2 + 3 + 4 + 6 + 8) / 5.0的结果)。


与问题核心无关的评论:

  • 该函数应该使用const std::vector&lt;T&gt;&amp;,因为它不会修改values
  • 如果您使用对std::accumulate 无效的T 调用函数(例如,不是算术),您将收到编译时错误。最上面的 if 必须是 if constexpr 才能按照您想要的方式工作。

【讨论】:

  • 听起来不错,现在在失败的情况下使用if constexpr:我应该像现在一样抛出std::runtime_error(...),还是应该使用:static_assert(dependent_false&lt;T&gt;::value, "Must be arithmetic");?两者的主要区别是什么...
  • @FrancisCugler static_assert 在编译时评估,而throw 在运行时评估。大概你会想在编译时检查尽可能多的东西。
  • 另一条与问题核心无关的评论:比使用 std::is_arithmetic 特征更好,您应该编写一个自定义特征(或概念)来检查,而不是 T 提供 + 和一个/。这样,可以使用模仿算术类型的类型(例如std::complex)。
  • 像往常一样使用std::accumulatestatic_cast&lt;T&gt;(0) 也是有问题的,例如/*unsigned*/ char 的平均值(有溢出)。
  • @wondra 这取决于你追求什么。如果您有非算术类型的替代重载,或者想要支持诸如“可以使用这种类型调用 array_average 吗?”之类的特性,请使用 SFINAE。否则,我更喜欢static_assert,因为它的简单性和错误消息可读性。如果您需要在运行时进行检查(不太可能,但可能,例如因为它是用户评估的一部分-指定的表达式),使用运行时错误。
猜你喜欢
  • 1970-01-01
  • 2022-11-21
  • 1970-01-01
  • 1970-01-01
  • 2020-06-24
  • 2017-02-11
  • 2018-10-19
  • 1970-01-01
  • 2017-02-26
相关资源
最近更新 更多