【问题标题】:How to convert float to fixed point (higher precision) in C++如何在 C++ 中将浮点数转换为定点数(更高精度)
【发布时间】:2021-11-01 00:02:39
【问题描述】:

我正在尝试在 C++ 中实现我自己的定点算术,以便(稍后)进行更高精度的计算。我在想类似的东西

class FixedPoint
{
int intPart;
unsigned long long fracPart[some number];
}

我认为如果我 - 例如加法 - 首先添加两个 fracPart[某个数字],如果它们溢出,则将 1 添加到 fracPart[某个数字 - 1] 等等。

但我坚持将双“d”转换为这样的类。 intPart = d 当然有效。然后做

double Temp = d - intPart;

给我小数部分。但是我如何正确地将它分配给 fracPart[0]?在十进制中,如果 long long 正好有 20 位,我可以只做 Temp * 100000000000000000000,这样 0.14 就变成 14000000000000000000。但是如果在二进制中,我取 d 的尾数位(53/54 位),将它们分配给 fracPart[0 ](64 位),添加隐藏位并将其左移 13 位(或 12,因为隐藏位),值是错误的。到目前为止,我在网上找到的任何内容都没有帮助...

【问题讨论】:

  • intPart = d 可能会溢出; d 可能比 INT_MAX 大得多
  • 忘掉分数吧,你还有更多基本问题要解决。您是否知道doubles 可以存储超过int 精度的数值。例如,当double 的值为1e100 时,您希望intPart 发生什么?那是 1 后跟一百个零。尝试将其放入您的 intPart 不会很好。
  • 当然浮点值的范围比定点值更大。但是在范围内,定点值可能更有用。这就是我问的原因。
  • 在我的天真中,我将小数部分(我将使用std::modf)乘以我的十进制精度并将其存储在一个截断其余部分的 int 中。对于整数部分,我会使用std::numeric_limits 来查看double 值是否实际上在可表示范围内
  • Temp 乘以 2^64(或 long long 中的位数)。

标签: c++


【解决方案1】:

忘记小数。使用 2 的幂。您的第一个小数部分应包含值为 2^-1、2^-2、... 2^-64 的位。浮点的好处是您可以轻松地通过 2 的幂来缩放您的值。换句话说,减去整数部分,然后乘以 2^64,然后取下一个整数部分,依此类推。像这样的东西应该适合你:

#include <cmath>
// using std::floor, std::ldexp
#include <cstdint>
// using std::int64_t, std::uint64_t
#include <cstdio>
// using std::printf


class FixedPoint
{
  std::int64_t ipart;
  std::uint64_t fpart[2];

public:
  explicit FixedPoint(double f) noexcept
  {
    // rounded down so that the fractional part is always positive
    ipart = std::floor(f);
    f -= ipart;
    for(std::uint64_t& fractional: fpart) {
      f = std::ldexp(f, 64);
      fractional = f;
      f -= fractional;
    }
  }
  operator double() const noexcept
  {
    double f = 0.;
    for(int i = 1; i >= 0; --i) {
      f += fpart[i];
      f = std::ldexp(f, -64);
    }
    f += ipart;
    return f;
  }
};



int main()
{
  double f1 = 123.4567;
  FixedPoint p(f1);
  double f2 = p;
  std::printf("%g = %g\n", f1, f2);
}

一些最后的想法:

  1. 我希望你知道有实际的库可以为你做这种事情吗?我认为这只是一个用浮点和定点弄湿你的脚的练习。否则停止并停止。 ;-)
  2. 我切换到 std::uint64_t 是因为在您的数据类型中使用标准精度会更方便。
  3. 使用 uint64_t 的一个缺点是 x86_64 中没有快速双精度 uint64_t 机器指令。使用 uint32_t 实际上可能更快。
  4. 整数部分使用 int 但小数部分使用较大的类型是没有意义的。由于对齐,您只需在结构中浪费 32 位空间,您可以将其用于更大范围。要么切换到 32 位,要么将 64 位用于整数部分,并将小数部分的大小(整个数组的大小)保持为 64 位的倍数,例如2 x 32 位
  5. 请注意,std::frexp 为您提供浮点数的指数和 [0.5, 1)(或零)范围内的归一化尾数。这将允许您将整数部分替换为任意大范围的指数,而不会损失精度。当然,此时您只是在软件中重新实现扩展精度浮点。

【讨论】:

【解决方案2】:

你应该

#include <boost/multiprecision.hpp>

是的,真的。实现你自己的 bignum 可能会很有趣且很有启发性,但你会搞砸的。关于你从未想过的事情。有很多小的边缘情况和需要正确处理的事情——尤其是在处理非整数 bignums 时。

Boost Libraries 做对了,而且非常容易在您自己的代码中使用。

(假设您不想包含整个 MP 库,您实际上可以只为您希望使用的 MP 类型包含一个更具体的文件。)

顺便说一句,fixed point 是一个整数,由一些 fixed 因子缩放。例如,假设您正在为银行业编写软件。您可以将值存储为百分之一美分,即(美元 * 10000)因子。你要做的不是固定点。

【讨论】:

  • 我猜你是对的。让它“有点”工作,但已经在一些边缘情况下打破了我的大脑......有人可以推荐一个仅限标题的更高精度库吗?出于某种原因,我几乎没有让任何第三方工作(已经尝试过提升)。总是有一些随机错误或找不到文件/目录(我可能应该进行全新安装(Windows),因为我做了很多安装/卸载/重新安装几个 Visual Studio 版本和一些编译器)...
  • cpp_bin_floatcpp_dec_float 都是仅标头的 Boost bigfloat 库。 :O)
  • ... 并且不工作。我可以很好地包含它们,但是在编译时,找不到其他文件的错误(它们与原始目录结构位于同一位置)... ;-)
  • 我帮不了你。只需在方便的地方解压缩 Boost,确保它在包含路径中,然后编译。有可能,就像您在 *net 上找到的任何库一样,您有时可能需要等待一周左右的时间才能让某人取消最新版本。在您等待时,该库的早期版本将具有可用的 MP 内容。
  • 这是我在使用 Visual Studio 时遇到的一个问题:大多数时候只是没有设置包含路径。它只是没有向我显示大多数设置。这里有什么想法吗?我想我会在 VM 中尝试全新的 windows/vs-install。如果它在那里工作,我的安装可能是 f****d。否则我不知道这里出了什么问题……
【解决方案3】:

在您的情况下,使用字符串会是一个有效的解决方案吗?您可以重载所有数学运算并像使用纸和笔一样实现它们。这是很多工作,您可能会遇到不平凡的效率问题,但这可能是一个可行的解决方案。 我不清楚您是否在将双精度转换为结构时遇到问题,但这是一个使用字符串的最小工作示例:

#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

int main()
{
    int prec=20;
    double a=1.3235335151561;
    int intpart=std::floor(a);
    double tmp=a-intpart;
    std::ostringstream os;
    os<<std::setprecision(prec)<<tmp;
    std::string str = os.str();
    std::cout<<str<<std::endl;
    int frac_part[prec];
    for(int i=0; i<prec; ++i){
        frac_part[i]=(int)str[i+2]-48;
        //jumps 0., and the ascii number for '0' is 48
        std::cout<<frac_part[i]<<" ";
    }
    std::cout<<"\n";

    }

当然,一旦你有了字符串,使用数组作为小数部分在我看来是多余的。

【讨论】:

  • int ... = std::floor(a); 将在很多方面打破大数字。使用lrint 转换为整数,使用frexp 分解数字。 floor not 用于将浮点数转换为整数,它用于 设置浮点数类型。
  • @KamilCuk 我不认为 lrint 或 llrint 在这里天生就更好。 1. 对于巨大的数字,不动点的整个概念无论如何都会失效。 floor 后跟 cast 将给出与 lrint 基本相同的结果,因为浮点数无论如何都没有任何小数位。 2.如果我们使用lrint,它可能会四舍五入。然后小数部分变为负数,我们必须处理它。它只会让一切变得更加复杂。恕我直言,我们可以使用 llrint,但只能使用 fesetround(3),这意味着舍入到负无穷大。
  • @Homer512 是的,完全正确。 frexp.
  • 我同意。但我只是想展示一个与问题正在解决的问题相关的最小示例。确切地说,您可以直接将整个数字转换为字符串,然后取“.”前后的数字。总是假设没有外部库的基于字符串的解决方案适合问题的范围
猜你喜欢
  • 2020-02-03
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-17
相关资源
最近更新 更多