6

我有在浮点数(表示一秒)和 int64(表示一纳秒)之间转换的代码,从浮点数中6小数位

int64_t nanos = f * 1000000000LL;

然而,许多存储在浮点数中的十进制值不能完全用二进制浮点数表示,所以我得到的结果就像14199999488我的浮点数是14.2f. 目前我通过计算小数点后的有效位数来解决这个问题

const float logOfSecs = std::log10(f);

int precommaPlaces = 0;
if(logOfSecs > 0) {
   precommaPlaces = std::ceil(logOfSecs);
}

int postcommaPlaces = 7 - precommaPlaces;
if(postcommaPlaces < 0) {
   postcommaPlaces = 0;
}

然后将浮点数打印成字符串,让 Qt 正确地舍入浮点数。然后我将字符串解析为一个前后逗号整数,并用整数算术将它们相乘。

const QString valueStr = QString::number(f, 'f', postcommaPlaces);
qint64 nanos = 0;
nanos += valueStr.section(".", 0, 0).toLongLong() * 1000000000LL;
if(postcommaPlaces) {
   nanos += valueStr.section(".", 1).toLongLong() * 
     std::pow(10.0, 9 - postcommaPlaces);
}

这工作正常,但我想知道是否有更好,也许更快的方法来做到这一点?

4

3 回答 3

3

通过将值存储在float损坏已经造成的情况下,您已经丢失了原始数字,无论它是什么。您可以猜测一个可能预期的值,然后进行四舍五入,或者如果您只是尝试为用户显示一个值,您可以四舍五入到小数位数。

int64_t相反,您可以通过在整个代码库中使用定点表示来解决所有这些问题,从不转换为/从float并避免在每次转换期间丢弃精度。

于 2013-01-10T16:31:21.293 回答
2

例如,如果您想四舍五入到小数点后一位

#include <iostream>

int main()
{
    float f = 14.2f;
    long long n = f * 1000000000LL;
    std::cout << "float: " << n << '\n';
    n = (f + 0.05) * 10;
    n *= 100000000LL;
    std::cout << "rounded: " << n << '\n';
    return 0;
}

小数点后两位是(f + 0.005) * 100, ..., 小数点后六位

n = ((long long)((f + 0.0000005) * 1000000)) * 1000LL;

如果要考虑有效数字(所有数字),则必须先取小数位log10(f),然后再调整四舍五入。

但是正如@MarkB 已经说过的,如果您int64_t首先使用,则根本不需要它。

于 2013-01-10T16:32:39.217 回答
0

如其他答案所述,四舍五入到任意数量的十进制数字与打印浮点数密切相关。由于正确执行的算法相当复杂,因此正确执行此操作的最简单方法是使用 printf 本身。

请注意,您不一定必须提供任意数量的数字,另一种方法是使用最短的小数,该小数将在基数 2 中原样转换回来。此类算法用于在 Scheme、Java、Python、Squeak/ 中打印浮点数Pharo 等……不幸的是,libm printf 和任何标准 C 库都不兼容。

方案甚至更好,因为它打印 * 当您施加固定数量的数字时数字不重要(* 表示任何数字在转换回基数 2 时都会产生相同的浮点数)。

在此问题http://code.google.com/p/pharo/issues/detail?id=4957中有一个名为 Float-asMinimalDecimalFraction.st 的附件,其中包含在 Smalltalk 中实现的与 Scheme 类似的打印算法,但输出一个分数(两个任意长度整数的比率)而不是 ASCII 字符串。

因此,例如,尽管 14.2f 在内部完全表示为 14.19999980926513671875 为时不晚,但您可以检索到正确舍入为它的最短小数部分是 (142/10)。

在 Smalltalk 中使用这样的代码,解决您的问题的方法很简单:

nanos := (floatingPointSeconds asMinimalDecimalFraction * 1e9) rounded.

但是上面的代码在后台使用了精确的算术(1e9是一个整数)和任意长度的整数。

请注意,在浮点数中执行乘法会很糟糕:

nanos := (aFloat * 1e9) asMinimalDecimalFraction rounded.

实际上,虽然 1e9 asFloat 转换是精确的,但它的有效位跨越 21 位,因此浮点乘法很可能会累积舍入错误并恶化检索小分数的问题。

尽管在技术上以某种方式回答了这个问题,但由于以下原因,我个人认为上述算法在实用上是不合适的:

  1. 在没有任意精度算术库的帮助的情况下使用低级 C/C++ 指令进行操作并不是获得结果的最快途径

  2. 它非常有限,因为它不适用于具有几个舍入误差的计算结果(它们在统计上需要很多数字)

  3. 如果您可以完全避免使用 Float 并使用 nanos int,那就太过分了

尽管如此,知道它的存在总是很高兴......

于 2013-01-10T21:12:09.687 回答