【问题标题】:Why does (int)55 == 54 in C++?为什么 (int)55 == 54 在 C++ 中?
【发布时间】:2010-10-07 22:14:35
【问题描述】:

所以我正在学习 C++。我已经完成了“C++ 编程语言”和“Effective C++”,并且正在运行 Project Euler。问题 1...邓佐。问题2...不是那么多。我正在使用 VS2008 的 Win32 控制台应用程序。

低于 400 万的斐波那契数列的所有偶数项的总和是多少?

它不起作用,所以我减少到 100 个测试用例...

这是我写的……

// Problem2.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "Project Euler Problem 2:\n\n";
    cout << "Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:\n\n";
    cout << "1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...\n\n";
    cout << "Find the sum of all the even-valued terms in the sequence which do not exceed four million.\n\n";
    cout << "Answer:  " << Solve();
}

double Solve() {
    int FibIndex = 0;
    double result = 0.0;
    double currentFib = GenerateNthFibonacciNumber(FibIndex);
    while (currentFib < 100.0){
        cout << currentFib << " " << (int)currentFib << " " << (int)currentFib % 2 << "\n";
        if ((int)currentFib % 2 == 0){
            result += currentFib;
            cout<<(int)currentFib;
        }
        currentFib = GenerateNthFibonacciNumber(++FibIndex);
    }
    return result;
}

double GenerateNthFibonacciNumber(const int n){
    //This generates the nth Fibonacci Number using Binet's Formula
    const double PHI = (1.0 + sqrt(5.0)) / 2.0;
    return ((pow(PHI,n)-pow(-1.0/PHI,n)) / sqrt(5.0));
}

这是输出...

欧拉计划问题2:

斐波那契的每个新术语 序列是通过添加 前两期。从 1 开始 和 2,前 10 个术语将是:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

求所有偶值的总和 序列中的项不 超过四百万。

0 0 0
1 1 1
1 1 1
2 2 0
3 3 1
5 5 1
8 8 0
13 13 1
21 21 1
34 34 0
55 54 0
89 89 1
答案:99

所以我有三列调试代码...从生成函数返回的数字,(int)generatedNumber 和 (int)generatedNumber % 2

所以在第 11 个学期我们有

55,54,0

为什么 (int)55 = 54?

【问题讨论】:

  • FWIW,斐波那契数列通常以 0. 0,1,1,2,3,5,...
  • 这是欧拉计划的错,我同意你的看法。我写的算法确实生成了前导 0,1。但这不会对答案造成任何问题(因为它正在寻找偶数项的总和)
  • 由于 double 类型本身具有有限的精度,因此您应该小心将其用于此类事情,因为 Binet 公式使用无限精度来获得结果。

标签: c++ fibonacci


【解决方案1】:

转换为 int 会截断数字 - 就像您调用 floor(currentFib) 一样。因此,即使 currentFib54.999999...(一个非常接近 55 的数字,打印时会四舍五入),(int)currentFib 也会产生 54。

【讨论】:

  • 那么我有一个新问题,为什么 cout 显示 55 54 0 而不是 54.9999999 54 0?
  • 因为 cout
  • AFAIK,这有点像 6 位数的精度。但重要的一点是它四舍五入到输出精度设置为 - 所以如果你的浮点数非常接近 55,除非你输出全精度,否则你会得到 55。
  • @Shog9 - 是否可以进行完全精确的 cout 打印,还是必须使用不同的功能?
  • @an0nym0usc0ward - 55 左右是对的,但如果你查看代码,生成“55”的函数充满了 sqrts 和 pow;这肯定会导致一些不准确的数字。
【解决方案2】:

由于浮点舍入,55 行正在计算类似 54.99999 的值。 将 double 转换为 int 会立即截断 .99999。

在我的机器上,打印一个显示 (currentFib-(int)currentFib) 的列会显示大约 1.42109e-14 的错误。所以它更像是 0.999999999999986。

【讨论】:

    【解决方案3】:

    好的,简短的回答是,在任何情况下都不应该 (int)55 == 54,所以你需要开始问自己相关的代码行到底在做什么。

    第一个问题:与类型转换相比,== 绑定的强度如何?

    【讨论】:

    • typecast 绑定得更难,在任何情况下 (int)(55 == 54) 都是 0 或 false。这是一个以幼稚的方式使用浮点的问题。
    • 大卫,查一下“苏格拉底方法”这个短语。
    • 虽然我自己支持苏格拉底方法,但它是否适用于 SO 上的答案/评论格式?
    • 是的,当有人不来分发答案时。
    • 我知道苏格拉底的方法,但这个问题似乎离得很远。话又说回来,苏格拉底在军队时因在行军中不知疲倦而享有盛誉……
    【解决方案4】:

    Shog9 说得对,如果要将事物转换为整数,则使用 double 类型解决此类问题并不是最好的方法。如果您的编译器支持它,您应该使用 long long 或其他一些 64 位整数类型,这几乎肯定会保存小于 400 万斐波那契数列的所有偶数项之和的结果。

    如果我们使用斐波那契数列遵循奇数奇偶奇偶奇偶模式这一事实......以下内容应该可以解决问题。

    ...
    unsigned int fib[3];
    fib[0]=1;
    fib[1]=1;
    fib[2]=2;
    
    unsigned long long sum=0;
    
    while(fib[2]<4000000)
    {
        sum+=fib[2];
    
        fib[0]=(fib[1]+fib[2]);
        fib[1]=(fib[2]+fib[0]);
        fib[2]=(fib[0]+fib[1]);
    }
    
    std::cout<<"The sum is: "<<sum<<". \n";
    ....
    

    这应该可以解决问题,可能还有更快的方法,但这个方法非常直接且易于阅读。

    看着它,我意识到你可能会用一个标准的无符号 32 位整数作为和数,但我会保留它以防万一。

    此外,您的代码对生成第 n 个斐波那契数函数进行了大量函数调用。一个体面的优化编译器会内联这些调用,但如果不这样做,事情就会变慢,因为函数调用比其他技术更昂贵。

    【讨论】:

    • 是的。我可以使用整数来完成整个事情,但我决定使用比内特的公式......
    • 但比内公式速度较慢,精度有限。对浮点数的任何操作都比对整数的相同操作慢,并且 sqrt 和 pow 函数也不是完全快速。对我来说,欧拉项目的部分意义在于学习优化代码的方法。
    【解决方案5】:

    我 100% 同意 shog9 的回答 - 使用您用于计算斐波那契的算法,您必须非常小心浮点值。我发现页面cubbi.com: fibonacci numbers in c++ 似乎显示了其他获取它们的方式。

    我四处寻找一个好主意,了解如何让 GenerateNthFibonacciNumber 的实现处理它返回双精度 54.999999 的情况,但是当你转换为 int 或 long 时,你会得到 54。

    我在C++ Rounding 发现了一个看似合理的解决方案,我已在下面的代码中对其进行了调整。

    此外,这不是一个巨大交易,但您可能希望预先计算 PHI,然后将其作为参数传递或将其作为全局引用 - 现在您每次调用功能。

    double GenerateNthFibonacciNumber(const int n)
    {
            //This generates the nth Fibonacci Number using Binet's Formula   
            const double PHI = (1.0 + sqrt(5.0)) / 2.0;
            double x = ((pow(PHI,n)-pow(-1.0/PHI,n)) / sqrt(5.0));
            // inspired by http://www.codingforums.com/archive/index.php/t-10827.html
            return ((x - floor(x)) >= 0.5) ? ceil(x) : floor(x);
    }
    

    最后,这是我如何重写您的 Solve() 方法,以便 GenerateNthFibonacciNumber(FibIndex) 仅在代码中的一个位置调用。我还将包含当前偶数斐波那契项的当前运行总计的列添加到您的输出中:

    double Solve() {
        long FibIndex = 0;
        double result = 0.0;
        double oldresult = 0.0;
        int done = 0;
        const double PHI = (1.0 + sqrt(5.0)) / 2.0;
    
        while (!done)
        {
            double currentFib = GenerateNthFibonacciNumber(++FibIndex);
            if ((int)currentFib % 2 == 0)
            {
                oldresult = result;
    
                if (currentFib >= 4000000.0)
                {
                    done = 1;
                }
                else
                {
                    result += currentFib;
                }
    
            }
            cout << currentFib << " " << (int)currentFib << " " << (int)currentFib % 2 << " " << (int)result << "\n";       
        }
        return result;
    }
    

    【讨论】:

      【解决方案6】:

      我知道这对你真正的问题没有帮助,但你提到你正在学习 C++。出于学习目的,我建议尽可能接近 ANSI。我认为那是 MSVC 上的 /Za(您可能正在使用),或者 GCC 上的 -ansi -pedantic

      特别是,您应该为main 使用这些签名之一,直到您有充分的(特定于平台的)理由不这样做:

      int main(int argc, char *argv[]);
      int main(int argc, char **argv); // same as the first
      int main();
      

      ...而不是任何特定于平台的版本,例如这个(仅限 Windows)示例:

      #include <windows.h> // defines _TCHAR and _tmain
      int _tmain(int argc, _TCHAR* argv[]); // win32 Unicode vs. Multi-Byte
      

      【讨论】:

        【解决方案7】:

        以上所有关于整数数学不要使用浮点值的建议都值得注意!

        如果您想对正浮点值进行整数“舍入”,以便小数部分小于 0.5 的值舍入到下一个最小整数,而小数部分的值是 0.5 或更大的值舍入到下一个更高的整数,例如

        0.0 = 0
        0.1 = 0
        0.5 = 1
        0.9 = 1
        1.0 = 1
        1.1 = 1
        ...etc...    
        

        将 0.5 添加到您要投射的值。

        double f0 = 0.0;
        double f1 = 0.1;
        double f2 = 0.5;
        double f3 = 0.9;
        
        int i0 = ( int )( f0 + 0.5 );  // i0 = 0
        int i1 = ( int )( f1 + 0.5 );  // i1 = 0
        int i2 = ( int )( f2 + 0.5 );  // i2 = 1
        int i3 = ( int )( f3 + 0.5 );  // i3 = 1
        

        【讨论】:

          【解决方案8】:

          破解生成的代码,有时浮点数的行为与我们在长表达式中使用时的想法不同...破解代码并检查它。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-08-25
            • 1970-01-01
            • 2019-11-08
            • 1970-01-01
            • 2023-03-21
            • 2014-09-18
            • 1970-01-01
            • 2019-11-12
            相关资源
            最近更新 更多