我目前正在MS中记录某些帧时间,而不是滴答声。我知道这可能是个问题,因为我们将所有帧时间(以MS为单位)加在一起,然后除以帧数。由于浮点精度,可能导致不良结果。
将所有的滴答计数加在一起,然后最后一次转换为MS,将更有意义。
但是,我想知道少量样品的实际差异是多少?我希望有900-1800个样本。会不会是个问题?
我已经做了一个小例子,并在GCC 4.9.2上运行它:
// Example program
#include <iostream>
#include <string>
int main()
{
float total = 0.0f;
double total2 = 0.0f;
for(int i = 0; i < 1000000; ++i)
{
float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
total += r;
total2 += r;
}
std::cout << "Total: " << total << std::endl;
std::cout << "Total2: " << total2 << std::endl;
}
结果:
总计:500004总计2:500007
据我所知,使用一百万个值,我们并不会损失很多精度。尽管我不确定我写的是合理的测试还是实际测试我想测试的东西。
所以我的问题是,在精度成为问题之前,我可以添加多少个浮点数?我希望我的值介于1到60 MS之间。我希望最终精度在1毫秒内。我有900-1800的值。
值示例:15.1345f持续15毫秒。
答案 0 :(得分:4)
使用以下有关问题陈述的假设(时间有效地以.06等值给出60毫秒),如果将.06转换为float
并将其相加1800次,则计算结果为107.99884796142578125。这与数学结果108.000的差异超过了0.001。因此,计算结果有时会与数学结果相差超过1毫秒,因此在这些条件下无法实现问题中期望的目标。 (对问题陈述的进一步改进和替代的计算方法可能可以达到目标。)
假设我们在[1,60]中有1800个整数值,并使用float
转换为float y = x / 1000.f;
,其中所有操作均使用IEEE-754基本32位二进制浮点数和正确的四舍五入。
从1到60到float
的转换是准确的。除以1000的误差最多为½ULP(.06),即½•2 −5 •2 −23 = 2 −29 < / sup>。 1800这样的错误最多为1800•2 −29 。
当相加所得的float
值时,每次相加的误差可能最多为1/2 ULP,其中ULP是当前结果的误差。为了进行宽松的分析,我们可以将其与最终结果的ULP绑定在一起,最终结果的ULP最多为1800•.06 = 108,ULP为2 6 •2 −23 = 2 −17 。因此,每个1799个附加项的误差最多为2 −17 ,因此,这些附加项的总误差最多为1799•2 −18 。
因此,除法和加法运算的总误差最多为1800•2 −29 + 1799•2 −18 ,约为.006866。
这是一个问题。我希望对加法中的错误进行更好的分析将使错误范围减半,因为它是从0到总数的算术级数,但是仍然留下了.003以上的潜在错误,这意味着总和可能为关闭几毫秒。
请注意,如果将时间加为整数,则最大的潜在总和为1800•60 = 108,000,这远低于float
中无法表示的第一个整数(16,777,217)。在float
中添加这些整数将没有错误。
.003的界限很小,足以对问题进行一些附加约束,并且可能进行一些附加分析,将其推到.0005以下,在这种情况下,计算结果将始终与正确的数学结果足够接近,以至于将计算结果四舍五入到最接近的毫秒数将产生正确的答案。
例如,如果知道时间在1到60毫秒之间,但总时间总是小于7.8秒,就足够了。
答案 1 :(得分:3)
由于您已经描述了以毫秒为单位测量单个计时,因此最好在最终除以整数之前使用整数值累计这些计时:
std::milliseconds duration{};
for(Timing const& timing : timings) {
//Lossless integer accumulation, in a scenario where overflow is extremely unlikely
//or possibly even impossible for your problem domain
duration += std::milliseconds(timing.getTicks());
}
//Only one floating-point calculation performed, error is minimal
float averageTiming = duration.count() / float(timings.size());
请考虑以下两种累积值的方法:
#include<iostream>
int main() {
//Make them volatile to prevent compilers from optimizing away the additions
volatile float sum1 = 0, sum2 = 0;
for(float i = 0.0001; i < 1000; i += 0.0001) {
sum1 += i;
}
for(float i = 1000; i > 0; i -= 0.0001) {
sum2 += i;
}
std::cout << "Sum1: " << sum1 << std::endl;
std::cout << "Sum2: " << sum2 << std::endl;
std::cout << "% Difference: " << (sum2 - sum1) / (sum1 > sum2 ? sum1 : sum2) * 100 << "%" << std::endl;
return 0;
}
在某些机器上(特别是没有IEEE754 float
的机器),结果可能有所不同,但是在我的测试中,第二个值与第一个值相差3%,相差1300万。那可能非常重要。
像以前一样,最好的选择是最小化使用浮点值执行的计算的数量,直到将它们用作浮点值之前的最后一个可能步骤。这样可以将准确性损失降到最低。
答案 2 :(得分:1)
就它的价值而言,下面的一些代码演示了是的,在1800个项目之后,一次简单的累加可能会超过1毫秒,这是不正确的,但是Kahan求和保持了所需的准确性。
#include <iostream>
#include <iterator>
#include <iomanip>
#include <vector>
#include <numeric>
template <class InIt>
typename std::iterator_traits<InIt>::value_type accumulate(InIt begin, InIt end)
{
typedef typename std::iterator_traits<InIt>::value_type real;
real sum = real();
real running_error = real();
for (; begin != end; ++begin)
{
real difference = *begin - running_error;
real temp = sum + difference;
running_error = (temp - sum) - difference;
sum = temp;
}
return sum;
}
int main()
{
const float addend = 0.06f;
const float count = 1800.0f;
std::vector<float> d;
std::fill_n(std::back_inserter(d), count, addend);
float result = std::accumulate(d.begin(), d.end(), 0.0f);
float result2 = accumulate(d.begin(), d.end());
float reference = count * addend;
std::cout << " simple: " << std::setprecision(20) << result << "\n";
std::cout << " Kahan: " << std::setprecision(20) << result2 << "\n";
std::cout << "Reference: " << std::setprecision(20) << reference << "\n";
}
对于此特定测试,似乎至少对于我尝试的输入值来说,双精度就足够了。但是,老实说,我仍然对此持怀疑态度,尤其是当穷举测试不合理时,更好的技术很容易获得。