C++ 不使用 modf() 获取浮点数的小数部分

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2594913/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 00:01:27  来源:igfitidea点击:

Getting the fractional part of a float without using modf()

c++cmathbit-manipulation

提问by knight666

I'm developing for a platform without a math library, so I need to build my own tools. My current way of getting the fraction is to convert the float to fixed point (multiply with (float)0xFFFF, cast to int), get only the lower part (mask with 0xFFFF) and convert it back to a float again.

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

However, the imprecision is killing me. I'm using my Frac() and InvFrac() functions to draw an anti-aliased line. Using modfI get a perfectly smooth line. With my own method pixels start jumping around due to precision loss.

然而,不精确正在杀死我。我正在使用我的 Frac() 和 InvFrac() 函数来绘制一条抗锯齿线。使用modfI 得到一条完美流畅的线条。由于精度损失,使用我自己的方法像素开始跳跃。

This is my code:

这是我的代码:

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;
}

Thanks in advance!

提前致谢!

回答by Daniel Bingham

If I understand your question correctly, you just want the part after the decimal right? You don't need it actually in a fraction (integer numerator and denominator)?

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

So we have some number, say 3.14159and we want to end up with just 0.14159. Assuming our number is stored in float f;, we can do this:

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

f = f-(long)f;

Which, if we insert our number, works like this:

如果我们插入我们的数字,它的工作方式如下:

0.14159 = 3.14159 - 3;

What this does is remove the whole number portion of the float leaving only the decimal portion. When you convert the float to a long, it drops the decimal portion. Then when you subtract that from your original float, you're left with onlythe decimal portion. We need to use a long here because of the size of the floattype (8 bytes on most systems). An integer (only 4 bytes on many systems) isn't necessarily large enough to cover the same range of numbers as a float, but a longshould be.

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

回答by AVB

As I suspected, modfdoes not use any arithmetic per se-- it's all shifts and masks, take a look here. Can't you use the same ideas on your platform?

正如我怀疑,modf不使用任何算术本身-这是所有班次和面具,看看这里。你不能在你的平台上使用相同的想法吗?

回答by Bill Lynch

I would recommend taking a look at how modf is implemented on the systems you use today. Check out uClibc's version.

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

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

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

(For legal reasons, it appears to be BSD licensed, but you'd obviously want to double check)

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

Some of the macros are defined here.

这里定义一些宏。

回答by Mark Ransom

There's a bug in your constants. You're basically trying to do a left shift of the number by 16 bits, mask off everything but the lower bits, then right shift by 16 bits again. Shifting is the same as multiplying by a power of 2, but you're not using a power of 2 - you're using 0xFFFF, which is off by 1. Replacing this with 0x10000 will make the formula work as intended.

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

回答by Bruno Brant

I'm not completly sure, but I think that what you are doing is wrong, since you are only considering the mantissa and forgetting the exponent completely.

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

You need to use the exponent to shift the value in the mantissa to find the actual integer part.

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

For a description of the storage mechanism of 32bit floats, take a look here.

有关 32 位浮点数存储机制的说明,请查看此处

回答by Michael Dorgan

Why go to floating point at all for your line drawing? You could just stick to your fixed point version and use an integer/fixed point based line drawing routine instead - Bresenham's comes to mind. While this version isn't aliased, I know there are others that are.

为什么要为您的线条绘制完全使用浮点数?您可以坚持使用定点版本并使用基于整数/定点的线条绘制例程来代替 - Bresenham 的想法。虽然这个版本没有别名,但我知道还有其他版本。

Bresenham's line drawing

布雷森汉姆的线描

回答by Victor Engel

Seems like maybe you want this.

好像你想要这个。

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

回答by emlai

One option is to use fmod(x, 1).

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

回答by TheWhitde

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

Simple and works for -ve and +ve

简单且适用于 -ve 和 +ve

回答by MSalters

Your method is assuming that there are 16 bits in the fractional part (and as Mark Ransom notes, that means you should shift by 16 bits, i.e. multiply by by 0x1000). That might not be true. The exponent is what determines how many bit there are in the fractional part.

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

To put this in a formula, your method works by calculating (x modf 1.0)as ((x << 16) mod 1<<16) >> 16, and it's that hardcoded 16 which should depend on the exponent - the exact replacement depends on your float format.

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