背景:
我一直在研究以下问题,来自S. Skiena的“编程挑战:编程竞赛培训手册”中的“旅程”:
一群学生是每年旅行一次的俱乐部的成员 不同的地点。他们过去的目的地包括 印第安纳波利斯,凤凰城,纳什维尔,费城,圣何塞和亚特兰大。 今年春天,他们计划去埃因霍温旅行。
该小组事先同意分摊费用,但事实并非如此 实际分享每一笔费用。因而个人在 团体支付特定的东西,如餐饮,酒店,出租车, 和机票。旅行结束后,每个学生的费用都计算在内 交换金钱,以便每个人的净费用相同 在一美分之内。在过去,这种货币兑换一直很乏味 耗时的。你的工作是从费用清单中计算出来 为了均衡而必须转手的最低金额 (一分钱内)所有学生的费用。
输入
标准输入将包含多次旅行的信息。每 trip包含一个包含正整数n的行,表示 这次旅行的学生人数。接下来是n行输入, 每个都包含学生用美元和美分支付的金额。 没有超过 1000名学生,没有学生花费超过 的 $ 10,000.00 即可。包含0的单行跟随信息 最后一次旅行。
输出
对于每次旅行,输出一行说明总金额 美元和美分,必须交换以平衡学生 成本。
我用以下代码解决了这个问题:
/*
* the-trip.cpp
*/
#include <iostream>
#include <iomanip>
#include <cmath>
int main( int argc, char * argv[] )
{
int students_number, transaction_cents;
double expenses[1000], total, average, given_change, taken_change, minimum_change;
while (std::cin >> students_number) {
if (students_number == 0) {
return 0;
}
total = 0;
for (int i=0; i<students_number; i++) {
std::cin >> expenses[i];
total += expenses[i];
}
average = total / students_number;
given_change = 0;
taken_change = 0;
for (int i=0; i<students_number; i++) {
if (average > expenses[i]) {
given_change += std::floor((average - expenses[i]) * 100) / 100;
}
if (average < expenses[i]) {
taken_change += std::floor((expenses[i] - average) * 100) / 100;
}
}
minimum_change = given_change > taken_change ? given_change : taken_change;
std::cout << "$" << std::setprecision(2) << std::fixed << minimum_change << std::endl;
}
return 0;
}
我的原始实现有float
而不是double
。它正在处理描述中提供的小问题实例,我花了很多时间试图找出问题所在。
最后我发现我必须使用double
精度,显然编程挑战测试中的一些重要输入使我的算法浮动失败。
问题:
鉴于输入可以有1000名学生,每个学生最多可以花费10,000美元,我的total
变量必须存储一些最大数量的
10,000,000。
我应该如何决定需要哪种精度? 有什么东西可以给我一个提示,浮动不足以完成这项任务吗?
我后来才意识到,在这种情况下,我可以完全避免浮点,因为我的数字适合整数类型,但我仍然有兴趣了解是否有办法预见float
不是在这种情况下足够精确。
答案 0 :(得分:3)
是否有什么东西应该给我一个浮动不足以完成这项任务的暗示?
事实上,在二进制浮点(float
和double
,如果你使用普通计算机)中,0.10根本不可表示,应该是提示。二进制浮点非常适合于开始时到达不准确的物理量,或者无论如何具有可判定的相等的合理数值系统的计算都是不准确的。货币金额的精确计算不是二元浮点的良好应用。
我应该如何决定需要哪种精度? ...我的总变量必须存储10,000,000的最大大小。
使用整数类型表示分数。根据您自己的推理,您不应该处理超过1,000,000,000美分的金额,因此long
应该足够了,但只需使用long long
并避免角落案件出现问题的风险
答案 1 :(得分:1)
10,000,000> 2 ^ 23因此您需要至少24位尾数,这是单精度提供的。由于中间舍入,最后一位可能会出错。
1位~3.321928位。
答案 2 :(得分:1)
正如您所说:切勿使用浮点变量来代表金钱。使用整数表示 - 可以是美分形式的一个大数字,也可以是本地货币的一小部分,或者是两个数字[这使得数学更加笨拙,但更容易看到/读取/写入两个数值单位]。
不使用浮点的动机是“通常不准确”。就像1/3不能使用十进制表示一样写成精确值,无论你编写多少三个,实际答案会有更多三分,二进制浮点值不能精确描述一些十进制值,你得到“您的0.20的值与客户所欠的0.20不匹配“ - 这没有意义,但那是因为”0.200000000001“和”0.19999999999“根据计算机不完全相同。最终,这些小的舍入错误会以某种方式导致一些大问题 - 无论是float
,double
还是extra_super_long_double
都会出现这种问题。
但是,如果您有这样的问题:如果我必须表示1000万的值,精度为单位的1/100,我需要多大的浮点变量,您的计算将变为:< / p>
float bigNumber = 10000000;
float smallNumber = 0.01;
float bits = log2(bigNumber/smallNumber);
cout << "Bits in mantissa needed: " << ceil(bits) << endl;
因此,在这种情况下,我们得到位为29.897,所以你需要30位(换句话说,float
不够好。
当然,如果你不需要一美元(或其他)的分数,你可以减少一些数字。即log2(10000000)
= 23.2 - 所以24位尾数 - &gt;仍然对float
来说太大了。