【问题标题】:Getting the fractional part of a float without using modf()在不使用 modf() 的情况下获取浮点数的小数部分
【发布时间】:2011-02-05 09:34:24
【问题描述】:

我正在开发一个没有数学库的平台,所以我需要构建自己的工具。我目前获取分数的方法是将浮点数转换为定点(乘以 (float)0xFFFF,强制转换为 int),仅获取下部(掩码为 0xFFFF)并再次将其转换回浮点数。

但是,不精确正在杀死我。我正在使用我的 Frac() 和 InvFrac() 函数来绘制抗锯齿线。使用modf 我得到了一条非常平滑的线。使用我自己的方法,由于精度损失,像素开始跳动。

这是我的代码:

const float fp_amount = (float)(0xFFFF);
const float fp_amount_inv = 1.f / fp_amount;

inline float Frac(float a_X)
{
    return ((int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv;
}

inline float Frac(float a_X)
{
    return (0xFFFF - (int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv;
}

提前致谢!

【问题讨论】:

  • fp_amount 不应该是 0x10000 而不是 0xFFFF?
  • 天哪。让它成为一个答案,这样我就可以接受它!你刚刚解决了我的整个准确性问题!
  • 如果评论修复了为什么没有人编辑原始代码?

标签: c++ c math bit-manipulation


【解决方案1】:

如果我正确理解您的问题,您只需要小数点后的部分,对吗?您实际上不需要分数(整数分子和分母)?

所以我们有一些数字,比如3.14159,我们希望只得到0.14159。假设我们的号码存储在float f;,我们可以这样做:

f = f-(long)f;

如果我们插入我们的号码,其工作方式如下:

0.14159 = 3.14159 - 3;

这样做是删除浮点数的整数部分,只留下小数部分。当您将浮点数转换为长整数时,它会删除小数部分。然后,当您从原始浮点数中减去它时,您只剩下 小数部分。由于float 类型的大小(在大多数系统上为 8 字节),我们需要在这里使用 long。整数(在许多系统上只有 4 个字节)不一定大到足以覆盖与 float 相同的数字范围,但 long 应该是。

【讨论】:

  • 一个 if...then...else... 在经常使用的数学函数中?我的缓存,它哭了!
  • f 为负数时这是错误的。 (您添加了两个负数。)您根本不需要iff = f - (int) f。如果f 为负数,它将减去一个向零舍入的负整数。
  • 另外,您假设浮点数的整数部分适合 int。
  • 哦,好吧。这个答案既是最容易实现的,也是最快的(比我的方法快 20% 左右)。接受!
  • f 远远超出long 范围时,(long)f; 失败。 (long long)f; 扩大了范围,但仍然是同样的问题。
【解决方案2】:

正如我所怀疑的,modf 不使用任何算术本身 -- 都是移位和掩码,看看here。你不能在你的平台上使用同样的想法吗?

【讨论】:

    【解决方案3】:

    我建议您看看 modf 在您今天使用的系统上是如何实现的。查看 uClibc 的版本。

    http://git.uclibc.org/uClibc/tree/libm/s_modf.c

    (出于法律原因,它似乎是 BSD 许可的,但您显然需要仔细检查)

    一些宏定义为here

    【讨论】:

    • 为什么所有的位移,真的有那么大的速度增益?还是我的小 int 转换技巧有一些我遗漏的问题?
    • @Daniel Bingham:可能是后者。浮点数可能不会按照您在使用的平台上的想法进行编码,因此您的掩码可能已关闭。 @sharth:您的链接依赖于一些宏,我很难找到它们。你能试试运气并找到 EXTRACT_WORDS、INSERT_WORDS 和 GET_HIGH_WORD 的定义吗?
    • Int 可以浮动。从浮点数到 int 非常慢。
    【解决方案4】:

    您的常量中有一个错误。您基本上是在尝试将数字左移 16 位,屏蔽除低位以外的所有内容,然后再次右移 16 位。移位与乘以 2 的幂相同,但您没有使用 2 的幂 - 您使用的是 0xFFFF,它被 1 关闭。将其替换为 0x10000 将使公式按预期工作。

    【讨论】:

    • 太糟糕了,原来的发布者没有编辑他的答案,如果这样做,事实上,纠正了问题。
    【解决方案5】:

    我不完全确定,但我认为你的做法是错误的,因为你只考虑尾数而完全忘记了指数。

    您需要使用指数移动尾数中的值以找到实际的整数部分。

    关于32bit floats的存储机制的说明,请看here

    【讨论】:

      【解决方案6】:

      为什么要为您的线条图使用浮点数?您可以坚持使用您的定点版本并使用基于整数/定点的线条绘制例程 - Bresenham's 浮现在脑海中。虽然这个版本没有别名,但我知道还有其他版本。

      Bresenham's line drawing

      【讨论】:

      【解决方案7】:

      好像你想要这个。

      float f = something;
      float fractionalPart = f - floor(f);
      

      【讨论】:

      • floor 也比强制转换慢。
      【解决方案8】:

      您的方法假设小数部分有 16 位(正如 Mark Ransom 所说,这意味着您应该移动 16 位,即乘以 0x1000)。那可能不是真的。指数决定了小数部分有多少位。

      要将其放入公式中,您的方法通过将(x modf 1.0) 计算为((x << 16) mod 1<<16) >> 16 来工作,它是硬编码的 16,它应该取决于指数 - 确切的替换取决于您的浮点格式。

      【讨论】:

        【解决方案9】:
        double frac(double val)
        {
            return val - trunc(val);
        }
        
        // frac(1.0) = 1.0 - 1.0 = 0.0 correct
        // frac(-1.0) = -1.0 - -1.0 = 0.0 correct
        // frac(1.4) = 1.4 - 1.0 = 0.4 correct
        // frac(-1.4) = -1.4 - -1.0 = -0.4 correct
        

        简单,适用于 -ve 和 +ve

        【讨论】:

          【解决方案10】:

          一种选择是使用fmod(x, 1)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-08-21
            • 2015-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多