【问题标题】:Converting from unsigned long long to float with round to nearest even从 unsigned long long 转换为浮点数到最近的偶数
【发布时间】:2010-12-09 16:41:37
【问题描述】:

我需要编写一个从 unsigned long long 舍入到 float 的函数,并且舍入应该是最接近的偶数。 我不能只做 C++ 类型转换,因为 AFAIK 标准没有指定舍入。 我正在考虑使用 boost::numeric,但在阅读文档后我找不到任何有用的线索。这可以使用那个库来完成吗? 当然,如果有替代品,我很乐意使用它。

任何帮助将不胜感激。

编辑:添加一个示例以使事情更清晰。 假设我想将 0xffffff7fffffffff 转换为它的浮点表示。 C++ 标准允许以下任一种:

  1. 0x5f7fffff ~ 1.9999999*2^63
  2. 0x5f800000 = 2^64

现在如果你添加round to最接近偶数的限制,只有第一个结果是可以接受的。

【问题讨论】:

  • 如何舍入一个整数值?最接近的甚至是i & ~1ull
  • 转换完成后对浮点值进行四舍五入。
  • 你确定你没有那个倒退吗?应该是floatunsigned long long
  • 是的,我确定。双向转换是相当合法的。
  • 你需要弄清楚你真正想要什么。 ULL按标准转换为float的结果可能精确也可能不精确,可以大于或小于原始值,不必表示整数,浮点数可以带小数。

标签: c++ boost floating-point rounding numeric


【解决方案1】:

由于源中有很多位无法在 float 中表示,并且您不能(显然)依赖语言的转换,所以您必须自己做。

我设计了一个可能会或可能不会帮助您的方案。基本上,float 中有 31 位表示正数,所以我选择源编号中的 31 个最高有效位。然后我保存并屏蔽所有低位。然后根据低位的值向上或向下舍入“新”LSB,最后使用static_cast 创建float

我留下了一些你可以根据需要删除的 couts。

const unsigned long long mask_bit_count = 31;

float ull_to_float2(unsigned long long val)
{
    // How many bits are needed?
    int b = sizeof(unsigned long long) * CHAR_BIT - 1;
    for(; b >= 0; --b)
    {
        if(val & (1ull << b))
        {
            break;
        }
    }

    std::cout << "Need " << (b + 1) << " bits." << std::endl;

    // If there are few enough significant bits, use normal cast and done.
    if(b < mask_bit_count)
    {
        return static_cast<float>(val & ~1ull);
    }

    // Save off the low-order useless bits:
    unsigned long long low_bits = val & ((1ull << (b - mask_bit_count)) - 1);
    std::cout << "Saved low bits=" << low_bits << std::endl;

    std::cout << val << "->mask->";
    // Now mask away those useless low bits:
    val &= ~((1ull << (b - mask_bit_count)) - 1);
    std::cout << val << std::endl;

    // Finally, decide how to round the new LSB:
    if(low_bits > ((1ull << (b - mask_bit_count)) / 2ull))
    {
        std::cout << "Rounding up " << val;
        // Round up.
        val |= (1ull << (b - mask_bit_count));
        std::cout << " to " << val << std::endl;
    }
    else
    {
        // Round down.
        val &= ~(1ull << (b - mask_bit_count));
    }

    return static_cast<float>(val);
}

【讨论】:

  • 谢谢!看起来很有希望。只是一个小修复:返回 static_cast(val) 以防我们有足够的位来准确表示。我将在离线状态下查看此解决方案。
  • @zr 如果你有足够的位来表示 val,那么你可以返回一个奇数。我掩盖了低位以防止这种情况,总是返回一个偶数。我怀疑它需要两个不同的早期返回,具体取决于它是否需要小于 24 位或小于 31 位。
【解决方案2】:

我在 Smalltalk 中为任意精度整数 (LargeInteger) 执行此操作,在 Squeak/Pharo/Visualworks/Gnu Smalltalk/Dolphin Smalltalk 中实现和测试,如果您可以阅读 Smalltalk 代码 http://smallissimo.blogspot.fr/2011/09/clarifying-and-optimizing.html ,甚至还写过博客。
加速算法的技巧是这个:符合 IEEE 754 的 FPU 将精确地舍入不精确操作的结果。所以我们可以承受 1 次不精确的操作,并让硬件为我们正确轮换。这让我们可以轻松处理前 48 位。但是我们不能承受两个不精确的操作,所以有时我们必须以不同的方式处理最低位...
希望代码有足够的文档记录:

#include <math.h>
#include <float.h>
float ull_to_float3(unsigned long long val)
{
    int prec=FLT_MANT_DIG ;             // 24 bits, the float precision
    unsigned long long high=val>>prec;  // the high bits above float precision
    unsigned long long mask=(1ull<<prec) - 1 ;      // 0xFFFFFFull a mask for extracting significant bits
    unsigned long long tmsk=(1ull<<(prec - 1)) - 1; // 0x7FFFFFull same but tie bit
    // handle trivial cases, 48 bits or less,
    // let FPU apply correct rounding after exactly 1 inexact operation
    if( high <= mask )
        return ldexpf((float) high,prec) + (float) (val & mask);
    // more than 48 bits,
    // what scaling s is needed to isolate highest 48 bits of val?
    int s = 0;
    for( ; high > mask ; high >>= 1) ++s;
    // high now contains highest 24 bits
    float f_high = ldexpf( (float) high , prec + s );
    // store next 24 bits in mid
    unsigned long long mid = (val >> s) & mask;
    // care of rare case when trailing low bits can change the rounding:
    // can mid bits be a case of perfect tie or perfect zero?
    if( (mid & tmsk) == 0ull )
    {
        // if low bits are zero, mid is either an exact tie or an exact zero
        // else just increment mid to distinguish from such case
        unsigned long long low = val & ((1ull << s) - 1);
        if(low > 0ull) mid++;
    }
    return f_high + ldexpf( (float) mid , s );
}

奖励:此代码应根据您的 FPU 舍入模式进行舍入,因为我们隐含地使用 FPU 通过 + 操作执行舍入。
但是,请注意标准 如果您总是想四舍五入到最接近的偶数,无论当前的舍入模式如何,那么您必须在以下情况下增加高位:

  • 中间位> tie,其中 tie=1ull
  • 中间位 == 平和(低位 > 0 或高位为奇数)。

编辑:
如果您坚持从四舍五入到最近的平局,那么另一种解决方案是使用非相邻部分(fhigh,flow)和(fmid)的 Shewchuck EXPANSION-SUM,请参阅http://www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps

#include <math.h>
#include <float.h>
float ull_to_float4(unsigned long long val)
{
    int prec=FLT_MANT_DIG ;             // 24 bits, the float precision
    unsigned long long mask=(1ull<<prec) - 1 ; // 0xFFFFFFull a mask for extracting significant bits
    unsigned long long high=val>>(2*prec);     // the high bits
    unsigned long long mid=(val>>prec) & mask; // the mid bits
    unsigned long long low=val & mask;         // the low bits
    float fhigh = ldexpf((float) high,2*prec);
    float fmid  = ldexpf((float) mid,prec);
    float flow  = (float) low;
    float sum1 = fmid + flow;
    float residue1 = flow - (sum1 - fmid);
    float sum2 = fhigh + sum1;
    float residue2 = sum1 - (sum2 - fhigh);
    return (residue1 + residue2) + sum2;
}

这使得无分支算法具有更多操作。它可能适用于其他舍入模式,但我让您分析论文以确保...

【讨论】:

    【解决方案3】:

    8 字节整数和浮点格式之间的可能性很容易解释,但实现起来就不那么简单了!

    下一段是关于 8 字节有符号整数可以表示的内容。

    1 (2^0) 和 16777215 (2^24-1) 之间的所有正整数都可以在 iEEE754 单精度(浮点数)中精确表示。或者,准确地说,2^0 和 2^24-2^0 之间的所有数字,增量为 2^0。下一个可精确表示的正整数范围是 2^1 到 2^25-2^1,增量为 2^1,依此类推,直到 2^39 到 2^63-2^39,增量为 2^39。

    无符号 8 字节整数值最多可以表示为 2^64-2^40,增量为 2^40。

    单精度格式并不止于此,而是一直持续到 2^103 到 2^127-2^103 的范围,增量为 2^103。

    对于 4 字节整数(长整数),最高浮点范围为 2^7 到 2^31-2^7,增量为 2^7。

    在 x86 架构上,浮点指令集支持的最大整数类型是 8 字节有符号整数。 2^64-1 无法通过常规方式加载。

    这意味着对于表示为“2^i,其中 i 是整数 >0”的给定范围增量,所有以位模式 0x1 到 2^i-1 结尾的整数将无法在该范围内精确表示一个浮子 这意味着您所说的向上舍入实际上取决于您正在工作的范围。如果您的范围的粒度,尝试向上舍入 1 (2^0) 或 16 (2^4) 是没有用的是 2^19。

    如果您尝试进行以下转换,您建议执行的操作(将 2^63-1 舍入为 2^63)可能会导致(长整数格式)溢出:longlong_int=(long long) ((float ) 2^63).

    看看我写的这个小程序(用 C 语言),它应该有助于说明什么是可能的,什么是不可能的。

    int main (void)
    {
      __int64 basel=1,baseh=16777215,src,dst,j;
      float cnvl,cnvh,range;
      int i=0;
    
      while (i<40)
      {
        src=basel<<i;
        cnvl=(float) src;
        dst=(__int64) cnvl;    /* compare dst with basel */
    
        src=baseh<<i;
        cnvh=(float) src;
        dst=(__int64) cnvh;    /* compare dst with baseh */
    
        j=basel;
        while (j<=baseh)
        {
          range=(float) j;
          dst=(__int64) range;
    
          if (j!=dst) dst/=0;
    
          j+=basel;
        }
    
        ++i;
      }
      return i;
    }
    

    此程序显示可表示的整数值范围。它们之间存在重叠:例如 2^5 在所有范围内都可以表示,其下边界为 2^b 其中 1=

    【讨论】:

      猜你喜欢
      • 2016-12-20
      • 1970-01-01
      • 2016-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多