在浮点和双精度之间进行选择

Choosing between float and double

本文关键字:行选 选择 之间 双精度      更新时间:2023-10-16

背景:

我一直在处理以下问题,"旅行"从"编程挑战:编程竞赛培训手册"由S.Skiena:

一群学生是一个俱乐部的成员,该俱乐部每年都会前往不同的位置。他们过去的目的地包括印第安纳波利斯、菲尼克斯、纳什维尔、费城、圣何塞和亚特兰大。今年春天,他们计划去埃因霍温旅行。

集团事先同意平均分摊费用,但事实并非如此在发生每一笔费用时都要分摊。因此团体支付特定的费用,如餐费、酒店费、出租车费,和机票。旅行结束后,统计每个学生的费用货币交换使得每个人的净成本相同一美分以内。在过去,这种货币兑换一直很乏味耗时。你的工作是根据开支清单计算为了平衡必须转手的最低金额(一分钱以内)所有学生的费用。

输入

标准输入将包含多个行程的信息。每个跳闸由一条包含正整数n的线组成,n表示旅行中的学生人数。这之后是n行输入,每个都包含学生花费的美元和美分。学生人数不超过1000人,且没有学生花费超过10000.00美元最后一次旅行。

输出

对于每次旅行,输出一行说明总金额,以美元和美分,必须交换才能使学生的成本。

(粗体是我的,在这里预订,在这里网站)

我用以下代码解决了这个问题:

/*
 * 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个学生,每个学生最多可以花费10000$,我的total变量必须存储一个最大大小为10000000.

我应该如何决定需要哪种精度?有没有什么东西应该给我一个提示,float对于这个任务来说是不够的?

后来我意识到,在这种情况下,我本可以避免浮点运算,因为我的数字适合整数类型,但我仍然有兴趣了解是否有办法预见float在这种情况中不够精确。

有没有什么东西应该给我一个提示,float对于这个任务来说是不够的?

0.10在二进制浮点中根本不可表示(如果使用普通计算机,则floatdouble都是)这一事实应该是提示。二进制浮点非常适合一开始就不准确的物理量,或者无论是具有可判定等式的合理数值系统,都不准确的计算。精确计算货币金额不是二进制浮点的好应用。

我应该如何决定需要哪种精度…我的总变量必须存储一个最大大小为10000000的数字。

使用整数类型来表示美分数。根据你自己的推理,你不应该处理超过1000000000美分的金额,所以long应该足够了,但只需使用long long,就可以避免遇到麻烦的风险。

正如您所说:永远不要使用浮点变量来表示货币。使用整数表示法-可以是一个以美分或当地货币的小数形式表示的大数字,也可以是两个数字[这让数学运算有点尴尬,但更容易看到/读取/写入两个单位的值]。

不使用浮点的动机是它"经常不准确"。就像1/3不能用十进制表示法写为一个精确的值一样,无论你写了多少个三,实际的答案都会有更多的三,二进制浮点值不能精确地描述一些十进制值,你会得到"你的0.20值与客户欠的0.20不匹配"-这没有意义,但这是因为根据计算机的说法,"0.200000000001"answers"0.19999999999"并不完全相同。最终,这些微小的舍入误差将以某种方式引发一些大问题——无论是floatdouble还是extra_super_long_double

然而,如果你有这样的问题:如果我必须用单位的1/100的精度表示一个1000万的值,我需要多大的浮点变量,你的计算就会变成:

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位尾数->对于float来说仍然太大。

10000000>2^23,因此您至少需要24位尾数,这就是单精度所提供的。由于中间取整,最后一位可能出错。

1位~3.321928位。