【问题标题】:How to keep precision on int64_t = int64_t * float?如何保持 int64_t = int64_t * float 的精度?
【发布时间】:2016-04-26 09:00:17
【问题描述】:

我想通过[0.01..1.2] 范围内的因子对int64_t 执行校正,精度约为0.01。天真的实现是:

int64_t apply_correction(int64_t y, float32_t factor)
{
    return y * factor;
}

不幸的是,如果我将factor 转换为int32 或将y 转换为float,我将失去精度。

但是,如果我可以确保y 的最大值低于1<<56,我可以使用这个技巧:

(1<<8) * (y / (int32_t)(factor * (1<<8)))

如果我的输入值可以大于1&lt;&lt;56,我该如何解决这个问题?

情节转折:

我在 32 位架构上运行,其中 int64_t 是一种模拟类型,并且我不支持双精度。该架构是 Analog Devices 的 SHARC。

【问题讨论】:

  • y * (int_64t)factor; 有什么问题?
  • @luk32 因为factor 在 0.01-1.2 范围内,所以它不起作用。
  • 如果你不支持双精度,你可以为此编写一个库,或者只是使用大量的双精度/多精度库。但是使用 double 仍然对您没有帮助,因为它只有 53 位精度,因此您无法获得完整的 64 位精度
  • @fluter 哦,我将int64 误读为float64,完全不明白问题出在哪里。我什至心不在焉地重新输入int 思考abotu float。我想对我来说,即使是基本的大脑活动也为时过早。

标签: c integer-division single-precision


【解决方案1】:

如果您计算((int64_t)1 &lt;&lt; 57) * 100* 256,您将有一个有符号整数溢出,这将导致您的代码具有未定义的行为。相反,如果您使用 uint64_t 和值,那么您的代码将是定义良好但行为不端的。


但是,对于几乎高达 (1 &lt;&lt; 63 / 1.2) 的数字,也可以使用此功能。

如果 yuint64_t,您可以将原始数字拆分为最高有效 32 位,右移 32 位,最低有效 32 位,乘以 (int32_t)(factor * (1 &lt;&lt; 8))

那么在乘法之后你不会将最高有效位右移 8 位,而是左移 24 位;然后加起来:

uint64_t apply_uint64_correction(uint64_t y, float32_t factor)
{
    uint64_t most_significant = (y >> 32) * (uint32_t)(factor * (1 << 8));
    uint64_t least_significant = (y & 0xFFFFFFFFULL) * (uint32_t)(factor * (1 << 8));     
    return (most_significant << 24) + (least_significant >> 8);
}

现在,apply_uint64_correction(1000000000000, 1.2) 将生成 1199218750000apply_uint64_correction(1000000000000, 1.25) 将生成 1250000000000


其实如果你能保证factor的范围,你可以做的更精准:

uint64_t apply_uint64_correction(uint64_t y, float32_t factor)
{
    uint64_t most_significant = (y >> 32) * (uint32_t)(factor * (1 << 24));
    uint64_t least_significant = (y & 0xFFFFFFFFULL) * (uint32_t)(factor * (1 << 24));     
    return (most_significant << 8) + (least_significant >> 24);
}

apply_uint64_correction(1000000000000, 1.2) 会在我的电脑上提供1200000047683;如果 float32_t 具有 24 位尾数,这也是您可以获得的最大精度。


上述算法也适用于有符号正数,但由于负数的有符号移位是一个灰色区域,我会记下符号,然后将值转换为uint64_t,进行便携式计算,并且如果原始符号为负,则取反。

int64_t apply_correction(int64_t y, float32_t factor) {
    int negative_result = 0;
    uint64_t positive_y = y;
    if (y < 0) {
        negative_result = 1;
        positive_y = -y;
    }

    uint64_t result = apply_uint64_correction(positive_y, factor);
    return negative_result ? -(int64_t)result : result;
}

【讨论】:

  • 最重要部分的余数/分数会怎样?
  • @WeatherVane 哎呀一个快捷方式:D
  • @WeatherVane 已修复
【解决方案2】:

在整数空间里做怎么样?

/* factor precision is two decimal places */
int64_t apply_correction(int64_t y, float32_t factor)
{
    return y * (int32_t)(factor * 100) / 100;
}

这确实假设 y 不是很接近最大值,但它让你比 56 位更接近一点。

【讨论】:

  • 这个 int32_t(factor * 100) 不会在 C 中编译。
  • @AnttiHaapala:这里的任何解决方案都可能出现整数溢出,因为 int64_t 最多乘以 1.2,然后作为相同类型返回。我已经更新了我的答案以提及具体的注意事项。
  • @AnttiHaapala:“[0.01..1.2] 范围内的因子 ... y 的最大值低于 1”会溢出吗?
  • @alk "如果我的输入值可以大于 1
  • @JohnZwinck 这会让你接近到最大值。
【解决方案3】:

只是不要使用浮点数。

int64_t apply_correction(int64_t y, float32_t factor)
{
  int64_t factor_i64 = factor * 100f;

  return (y * factor_i64) / 100ll;
}

这是假设y * factor_i64 * 100 不会溢出。

【讨论】:

  • 这不是假设long long 在系统上可用吗?
  • @Qix 是的,确实是假设使用了标准 C。你也可以写(int64_t)0。无论如何,这并不重要,因为通过平衡进行隐式类型提升。也就是说,(y * factor) / 100; 是等效且安全的。我只是喜欢明确类型。
  • @cmaster 糟糕,已修复。
  • 标准 C99 C,当然。 long long 在 c89/c90/c95 中不可用,考虑到这是一个嵌入式系统,应该考虑到这一点。
  • @Qix 如果你在维护旧的东西,当然可以。但是,如果您的项目是一个新项目,那么仍然没有理由使用 C90。请注意,int64_tfloat32_t 类型是在 C99 中引入的。同样根据 SO C 标签政策:除非 OP 明确声明他们对该语言的旧版本感兴趣,否则始终假定他们使用的是标准的当前版本,即 C11。
猜你喜欢
  • 1970-01-01
  • 2010-10-16
  • 1970-01-01
  • 2012-11-16
  • 2021-08-06
  • 2018-05-20
  • 1970-01-01
  • 2011-11-19
  • 1970-01-01
相关资源
最近更新 更多