【问题标题】:When should I use integer for arithmetic operations instead of float/double什么时候应该使用整数而不是浮点数/双精度数进行算术运算
【发布时间】:2016-10-15 16:09:19
【问题描述】:

一些 SO 用户对我说,我不应该使用 float/double 来计算学生成绩之类的东西,请参阅最后的 cmets:SequenceEqual() is not equal with custom class and float values

“因为用整数进行所有算术运算,然后在最后一分钟转换为合适的显示格式,通常比尝试以浮点格式进行算术运算更容易、更安全。” em>

我试过他说的,但结果并不令人满意。

   int grade1 = 580;
   int grade2 = 210;
   var average = (grade1 + grade2) / 2;       
   string result = string.Format("{0:0.0}", average / 100);

结果是“3,0”

    double grade3 = 5.80d;
    double grade4 = 2.10d;
    double average1 = (grade3 + grade4) / 2;
    double averageFinal = Math.Round(average1);
    string result1 = string.Format("{0:0.0}", averageFinal);

结果 1 是 “4,0”

我希望 4,0 因为 3,95 应该导致 4,0。之所以有效,是因为我使用的是 Math.Round,它又只适用于双精度或小数。这不适用于整数。

那么我在这里做错了什么?

【问题讨论】:

  • 除非十进制引入了不可接受的性能瓶颈,否则我建议使用十进制而不是标准化整数。两种方法都适用于成绩(浮点和双精度不是),但小数往往更容易考虑。

标签: c# arithmetic-expressions


【解决方案1】:

首先,你提到的具体问题让我非常烦恼。您几乎不想在整数算术中解决问题,然后然后将其转换为浮点类型,因为计算将完全以整数完成。我希望 C# 编译器警告这一点;我经常看到这个。

第二,整数或小数运算优于双精度运算的原因是双精度运算只能完全准确地表示分母为 2 的幂的分数。当你说双精度数中的 0.1 时,你不会得到 1/10,因为 1/10 不是分母是 2 的任何幂的分数。你得到的分数是最接近的分数确实在分母中有2的幂。

这通常是“足够接近”,直到不是。当您有接近硬截止的微小错误时,这尤其令人讨厌。例如,你想说一个学生必须有 2.4 GPA 才能满足某些条件,而你所做的涉及分母为 2 的分数的计算恰好是 2.39999999999999999956...

现在,您不一定能摆脱十进制算术的这些问题;十进制算术也有同样的限制:它只能表示分母为十次方的分数。您尝试表示 1/3,每次计算都会得到一个小但非零的错误。

因此,标准建议是:如果您在进行涉及分子中十次幂的分数的计算(例如金融计算)时,如果您正在执行任何您期望精确算术的计算,请使用小数,或者完全进行计算 整数,适当缩放。如果您正在进行涉及 物理 量的计算,并且计算没有固有的“基础”,那么请使用“double”。

那么为什么要使用整数而不是小数,反之亦然?整数运算可以更小更快;小数需要更多的时间和空间。但最终您不应该担心这些微小的性能差异:选择最准确地反映您所从事的数学领域的数据类型,然后使用它。

【讨论】:

  • 你好 Eric,我给了 Dmitry 解决方案。请看我给他的答复。但我不认为你在某些方面关心;-)我不明白你所说的一切,我也认为有些混乱。我也不喜欢用整数做算术运算,然后像示例 1 中那样转换为 double。我更喜欢示例 2。我遵循您的标准建议并使用反映我做事的数学领域的数据类型。 1, 1.20,1.50,1.75,第四个赞来自我:P
【解决方案2】:

你需要“在最后一刻转换成合适的显示格式”

string result = string.Format("{0:0.0}", average / 100.0);

【讨论】:

  • 很酷,你发现了一些重要的事情。也许我需要一个更好的例子。我不知道。 string.format 从哪里知道是否从 3.95 舍入到 4,0 或到 3,9 ? string.format 中的 math.round 是否执行?
  • @Quantic 你会回答什么问题?我从不怀疑它会四舍五入到最接近的整数。
  • @Elisabeth 看到 0:0.0 了吗?这指定了十进制精度。例如:对于 2 位小数精度 0:0.2。 : 前面的 0 是变量参数索引。
  • @Elisabeth 哎呀,很抱歉造成混乱,我删除了该评论。 string.Format("{0:0.0}" ... ) 表示“使用 0.0 的 Custom Numeric Format String”,0.0 不四舍五入,它截断到小数点后一位。您的问题在此之前就存在,C# 将round integer division towards zero,在您的帖子中,您将整数除以整数,因此每一步都向下舍入。由于100.0 中的句点,此处Dmitry 的帖子隐式转换为double
  • 您得到了解决方案,因为您实际上解决了我要求的问题。另一方面,我必须说我应该在我的问题中更多地区分——我现在意识到了——因为实际上我对我的问题有另一个目标。埃里克·利珀特的回答更进一步。
【解决方案3】:

average 是一个 int,100 是一个 int。 当你做平均/100 时,你有一个整数除以一个整数,你会得到一个整数,但由于 3.95 不是整数,所以它被截断为 3。

如果你想得到一个浮点数或双精度数作为你的结果,双精度数或浮点数必须参与算术运算。

在这种情况下,您可以将结果转换为 double (double)average/100 或除以 double, average/100.0

您希望在最后一秒之前不要对浮点数/小数进行太多算术运算的唯一原因与您不只是在开始时插入长物理方程中的变量值的原因相同,您会失去精度.当您必须处理数字的浮点表示时,这是数值方法中非常重要的概念,例如机器 epsilon。

【讨论】:

    【解决方案4】:

    我认为您在给定的两个选项中选择了错误的选项。了解 Eric,如果您清楚自己正在执行浮点运算,例如舍入、平均等,那么他不会建议使用整数。不使用double 的原因是因为它们不能总是精确地表示十进制值(你不能在double 中精确地表示1.1)。

    如果您想使用浮点数学但仍保持最高 28 位有效数字的十进制精度,请使用 decimal

    decimal grade1 = 580;
    decimal grade2 = 210;
    var average = (grade1 + grade2) / 2;     
    average = Math.Round(average);  
    string result = string.Format("{0:0.0}", average);
    

    【讨论】:

    • 我刚刚意识到给你的建议来自@EricLippert。现在我正在等待闪电带我出去......
    • 哈哈,我已经想让你知道了……最好改写你的陈述,否则你的名字将被用于下一个 .NET Core 更新黑名单 ;-)
    • "to not use doube..." 那么这对 float 也有效吗?我看不出使用小数有什么区别,只是速度较慢...
    • 是的 float 具有相同的特性,只是数据大小和范围更小。使用decimal 的速度差异有问题吗?如果差异对您的应用程序整体的性能有显着影响,我会感到惊讶。
    • decimal 没有“完美的准确性”。它也不能表示“精确的十进制值”,它只是在小数字上具有更高的精度——要高得多,因为它不仅具有更小的范围,而且具有两倍的位。 double 使用 64 位从 10^-324 变为 10^308,但十进制 only 使用 128 变为 -7.9 x 10^28 到 7.9 x 10^28(接近零到 10^28)位。
    【解决方案5】:

    没那么容易。

    如果成绩是整数格式,则以整数形式存储即可。

    但除非您特别需要整数数学,否则我会在执行任何数学运算之前将 do decimal 转换为从数学中消除舍入错误。

    对于财务计算,建议使用小数。实际上后缀是 m 代表钱。

    decimal (C# Reference)

    decimal 关键字表示 128 位数据类型。相比 浮点类型,十进制类型具有更高的精度和 范围更小,这使得它适用于金融和货币 计算。小数类型的近似范围和精度 如下表所示。

    FloatDouble 是浮点类型。你得到的范围比小数更大,但精度更低。十进制的分数范围足够大。

    decimal grade3 = 5.80m;
    decimal grade4 = 2.10m;
    decimal average1 = (grade3 + grade4) / 2;
    
    int grade1 = 580;
    int grade2 = 210;
    var average = (grade1 + grade2) / 2;
    string result = string.Format("{0:0.0}", average / 100);  // 3.0
    Debug.WriteLine(result);
    
    decimal avg = ((decimal)grade1 + (decimal)grade2) / 200m;  // 3.95
    Debug.WriteLine(avg);
    Debug.WriteLine(string.Format("{0:0.0}", avg));  // 4.0
    

    【讨论】:

    • 我不在数据库中存储成绩。如果你是那个意思。我将分数存储为浮点数,因为像 5,5 这样的一半分数。成绩是根据分数计算出来的。
    • 我的答案是十进制(不是浮点数)。我有十进制的链接。我不推荐浮动。请参阅 Microsoft 的链接 - 建议使用小数进行财务计算。
    • 但这与财务计算无关。我计算一个学生的平均成绩和他的班级考试。
    • 所以您不想要最准确的平均值?您不想使用用于财务的东西吗?你看不出金钱计算和成绩的相似之处吗?您宁愿使用用于精度较低的大型科学计算的数据类型?
    • 首先我不想因为别人习惯了就用别人习惯的东西。 Perfect Accuracy 是超强的,但当良好的准确性足够时,如果它有效,为什么不选择它呢?在这件事上我不是教条主义的,而是以实际为导向的。
    猜你喜欢
    • 1970-01-01
    • 2010-10-22
    • 2020-09-24
    • 2014-10-07
    • 2016-02-18
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    相关资源
    最近更新 更多