C++中是否有类似于Java的BigDecimal的等效物?

23
我正在寻找一个能够执行十进制浮点运算的C++类。在查看http://speleotrove.com/decimal/时,发现有人编写了各种未维护的类。在查看decNumber++时,我找到了一些电子邮件,显示GCC最终将支持此功能。(正式称为ISO/IEC TR 24733)
我正在寻找的是可以用作替代float或double的东西,其他人在他们自己的项目中使用的东西。希望是开放源代码的。
谢谢!
编辑:我应该指出,我正在尝试使用它来表示价格。所以我需要精确的小数,而不是超大的小数。

2
gmplib 可能是一个选择?http://gmplib.org/ - Anycorn
1
在价格计算中,不应使用双精度或浮点数。 - JH.
8
我知道这点,所以我正在寻找类似于BigDecimal的东西。 - poindexter
9个回答

17

有一个巨大的库叫做GMP(GNU多精度库),支持这种操作,同时还有C++绑定,不过老实说C++接口有些陈旧而不太好用。

以下是文档中的示例,它创建了一个至少具有500位精度的浮点数f

mpf_class f(1.5, 500);

2
类似于 BigDecimal 的 mpf_class 也有一个字符串构造函数。Ubuntu 10.4 自带的 gmpxx 版本使用了部分模板特化,因此将其称为过时可能有些牵强。我认为我可以让它按我的意愿工作。恭喜,你得到了一个饼干,嗯,对号。 - poindexter
1
@poindexter:我所说的“过时”主要是指命名:得了吧,什么样的类名是“mpf_class”?!他们应该把它称为gmp::big_float或类似的名称。 - Konrad Rudolph
我认为这是与GNU命名约定作斗争的艰难之举。 :) - poindexter

9

1
已删除库部分。 - Alexander Oh

4

1
我想警告你不要使用cpp_dec_float,因为我在尝试使用它时遇到了糟糕的经历。当我尝试将59999996874999999999999999831除以31250000时,cpp_dec_float给出了0.00000003199...99946064282(中间有很多9),而不是正确的0.000000032。当我使用std::locale::globat()调用更改默认语言环境时,转换为字符串完全失败,因为它依赖于“经典”语言环境的行为,但不强制执行。不要使用。 - Peter Taran

3

我可能来晚了,但128位小数能行吗? 这些已被C++接受,并且至少自gcc-4.5以来gcc已经支持它们(现在我们正在使用4.9):

#include <iostream>
#include <decimal/decimal>

using namespace std;

int main()
{
  {
    std::decimal::decimal32 dn(.3), dn2(.099), dn3(1000), dn4(201);
    dn-=dn2;
    dn*=dn3;
    cout << "decimal32 = "  << (dn==dn4) << " : " << decimal32_to_double(dn) << endl;
  }

  {
    std::decimal::decimal64 dn(.3), dn2(.099), dn3(1000), dn4(201);
    dn-=dn2;
    dn*=dn3;
    cout << "decimal64 = "  << (dn==dn4) << " : " << decimal64_to_double(dn) << endl;
  }

  {
    std::decimal::decimal128 dn(.3), dn2(.099), dn3(1000), dn4(201);
    dn-=dn2;
    dn*=dn3;
    cout << "decimal128 = " << (dn==dn4) << " : " << decimal128_to_double(dn) << endl;
  }

  return 0;
}

注意,decimal32与float大小相等,decimal64与大多数double大小相等。因此,decimal128非常大。来自维基百科:Decimal128支持34个十进制数字的有效数字和指数范围为-6143到+6144,即±0.000000000000000000000000000000000×10−6143至±9.999999999999999999999999999999999×106144。(同样,±0000000000000000000000000000000000×10−6176至±9999999999999999999999999999999999×106111。)
mpfr库是任意精度二进制浮点数 - 不是任意精度十进制。这有所不同。

3

如果需要处理巨大的十进制数值,建议使用http://gmplib.org/库。我在C和C++中经常使用它。


3

使用 GMP(GNU多精度算术库)并将所有金额存储为分。如果您知道金额不会超过 2^32 分(即 42.949673 百万美元),则可以使用 32 位无符号整数(或使用 64 位无符号整数)简化操作。


JH,这不是一个好主意。请跳转到“金融计算”部分,了解一些可能失败的示例。http://introcs.cs.princeton.edu/91float/ - poindexter
如果你需要进行特定的计算,但不理解自己在做什么,那么这可能是一个坏主意。否则,使用浮点数/双精度来表示“价格”似乎有些高估和不正确。 - JH.
1
实际上,这个答案对我来说比其他大多数答案更有意义,其他答案建议使用GMP或MAPM这些任意精度库,它们使用的是2进制,这意味着像0.1这样的值永远无法被精确表示,无论您设置什么精度。把所有东西都存储为分(cents),换句话说,将每个值乘以100并使用int64_t似乎是一个完全可以接受的解决方案。至少所有的加法都是精确的。 - sam hocevar
更正我的上一条评论:MAPM 在内部确实使用十进制数字,因此它将能够精确表示 0.1 - sam hocevar
1
@poindexter提供的链接似乎已经消失了,但是可以通过WayBack Machine访问到它(http://web.archive.org/web/20110228024114/http://introcs.cs.princeton.edu/91float/)。 - Chris Kline
当我在量化交易中工作时,我们将所有股票价格乘以10的大幂,并将它们存储为32位整数。这种方法运行良好 - 而且非常快速 - 直到有人使用了正确的美元/日元汇率的倒数,导致所有股票价格增加了约10,000倍,超出了整数类型的范围。这被交易引擎捕捉到并相对快速地修复了。故事的寓意是:你可以使用这种方法,但要小心溢出。 - quant_dev

1
这是一个将BCMath PHP实现为C++的示例。有两个版本,一个用于Qt,另一个仅使用STL。
源代码:https://github.com/DesarrollosCuado/BCMath-for-Cpp
BCMath::bcscale(4); //Num Decimals
BCMath test("-5978");

test^=30; //Pow, only integers. Not work decimals.
std::cout<<"Result BigDecimal 1: "<<test.toString().c_str()<<std::endl;

test-=1.23; //sub
std::cout<<"Result BigDecimal 2: "<<test.toString().c_str()<<std::endl;

test*=1.23; //mul
std::cout<<"Result BigDecimal 3: "<<test.toString().c_str()<<std::endl;

test*=-1.23; //mul
std::cout<<"Result BigDecimal 4: "<<test.toString().c_str()<<std::endl;

BCMath::bcscale(70); //Num Decimals

BCMath randNum("-5943534512345234545.8998928392839247844353457");
BCMath pi("3.1415926535897932384626433832795028841971693993751058209749445923078164062862");

BCMath result1 = randNum + pi;
BCMath result2 = randNum - pi;
BCMath result3 = randNum * pi;
BCMath result4 = randNum / pi;

std::cout<<"Result Super Precision 1: "<<result1.toString().c_str()<<std::endl;
std::cout<<"Result Super Precision 2: "<<result2.toString().c_str()<<std::endl;
std::cout<<"Result Super Precision 3: "<<result3.toString().c_str()<<std::endl;
std::cout<<"Result Super Precision 4: "<<result4.toString().c_str()<<std::endl;

//Other example
BCMath::bcscale(4); //Num Decimals
std::cout<<"Other 1: "<<BCMath::bcmul("1000000.0134", "8.0234").c_str()<<std::endl;
std::cout<<"Other 2: "<<BCMath::bcadd("1000000.0134", "8.0234").c_str()<<std::endl;

std::cout<<"Compare 1:  "<<BCMath::bccomp("1", "2")<<std::endl;
std::cout<<"Compare 2:  "<<BCMath::bccomp("1.00001", "1", 3)<<std::endl;
std::cout<<"Compare 3:  "<<BCMath::bccomp("1.00001", "1", 5)<<std::endl;
std::cout<<"Compare 4:  "<<(BCMath("1")< BCMath("2"))<<std::endl;
std::cout<<"Compare 5:  "<<(BCMath("1")<=BCMath("2"))<<std::endl;
std::cout<<"Compare 6:  "<<(BCMath("1")> BCMath("2"))<<std::endl;
std::cout<<"Compare 7:  "<<(BCMath("1")>=BCMath("2"))<<std::endl;
std::cout<<"Compare 8:  "<<(BCMath("2")< BCMath("2"))<<std::endl;
std::cout<<"Compare 9:  "<<(BCMath("2")<=BCMath("2"))<<std::endl;
std::cout<<"Compare 10: "<<(BCMath("2")> BCMath("2"))<<std::endl;
std::cout<<"Compare 11: "<<(BCMath("2")>=BCMath("2"))<<std::endl;

std::cout<<"Round 1: "<<BCMath::bcround("123.01254").c_str()<<std::endl;
std::cout<<"Round 2: "<<BCMath::bcround("-123.01254", 3).c_str()<<std::endl;
std::cout<<"Round 3: "<<BCMath::bcround("123.01254", 2).c_str()<<std::endl;
pi.round(3);
std::cout<<"Round 4: "<<pi.toString().c_str()<<std::endl;

BCMath part1("-.123");
BCMath part2(".123");
BCMath part3("123");
std::cout<<"Int part 1: "<<part1.getIntPart().c_str()<<std::endl;
std::cout<<"Dec part 1: "<<part1.getDecPart().c_str()<<std::endl;
std::cout<<"Int part 2: "<<part2.getIntPart().c_str()<<std::endl;
std::cout<<"Dec part 2: "<<part2.getDecPart().c_str()<<std::endl;
std::cout<<"Int part 3: "<<part3.getIntPart().c_str()<<std::endl;
std::cout<<"Dec part 3: "<<part3.getDecPart().c_str()<<std::endl;

结果:

Result BigDecimal 1:  198005530669253749533290222782634796336450786581284861381777714804795900171726938603997395193921984842256586113024
Result BigDecimal 2:  198005530669253749533290222782634796336450786581284861381777714804795900171726938603997395193921984842256586113022.7700
Result BigDecimal 3:  243546802723182111925946974022640799493834467494980379499586589209898957211224134482916796088524041355975600919018.0071
Result BigDecimal 4:  -299562567349513997668914778047848183377416395018825866784491504728175717369805685413987659188884570867849989130392.1487

Result Super Precision 1:  -5943534512345234542.7583001856941315459727023167204971158028306006248941790250554076921835
Result Super Precision 2:  -5943534512345234549.0414854928737180228979890832795028841971693993751058209749445923078164
Result Super Precision 3:  -18672164360341183116.9114783895073349180904753962992796943871920962352436079118338887287186
Result Super Precision 4:  -1891885794154043400.2804849527556211973567525043250278948318788149660700494315139982452600

Other 1:  8023400.1075
Other 2:  1000008.0368

Compare 1:   -1
Compare 2:   0
Compare 3:   1
Compare 4:   true
Compare 5:   true
Compare 6:   false
Compare 7:   false
Compare 8:   false
Compare 9:   true
Compare 10:  false
Compare 11:  true

Round 1:  123.0125
Round 2:  -123.013
Round 3:  123.01
Round 4:  3.142

Int part 1:  -0
Dec part 1:  123
Int part 2:  0
Dec part 2:  123
Int part 3:  123
Dec part 3:  0

1

0

失败:"请注意,默认的MAPM库不是线程安全的。如果多个MAPM函数同时活动,MAPM内部数据结构可能会损坏。用户应确保只有一个线程执行MAPM函数。这通常可以通过调用操作系统来获取“信号量”或“关键代码段”来实现,因此操作系统将保证一次只有一个MAPM线程处于活动状态。" - poindexter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接