鱼C论坛

 找回密码
 立即注册
查看: 136|回复: 5

[已解决]为什么我的程序算出的结果比小甲鱼多一粒麦子?

[复制链接]
发表于 2025-4-16 19:04:02 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
#include <stdio.h>
#include <math.h>

double sum(int start , int end)
{
    double ret = 0;
    int i = start;

    while (i < end)
    {
        ret = ret + pow(2,i);
        i++;
    }
   
    return ret;
}

int main(void)
{
    double result;
    double weight;

    result = sum(0,64);
    weight = result / 25000;

    printf("舍罕王应该给予达依尔%.0f粒麦子!\n",result);
    printf("如果每25000粒麦子为1kg,那么应该给%.0fkg麦子!\n",weight);

    return 0;
}
最佳答案
2025-4-17 00:07:25
本帖最后由 jackz007 于 2025-4-17 01:00 编辑

        应该是采用 double 数据类型累加所产生的计算误差。
        请编译、运行下面的代码,这个代码会按长整型和双精度浮点数分别计算累加结果,从打印结果看,一开始,二者累加结果均一致,但是,当 i = 53 的时候,累加结果开始出现错位,双精度数的累加结果比长整型大 1。
  1. #include <math.h>

  2. double sum(int start , int end)
  3. {
  4.     double ret1 = 0.0                                                   ;
  5.     unsigned long long ret2 = 0LL                                       ;
  6.     int i = start                                                       ;

  7.     while (i < end)
  8.     {
  9.         ret1 += pow(2 , i)                                              ;
  10.         ret2 += 1LL << i                                                ;
  11.         printf("%2d , %.0lf , %llu\n" , i , ret1 , ret2)                ;
  12.         i ++                                                            ;
  13.     }
  14.     return ret1                                                         ;
  15. }

  16. int main(void)
  17. {
  18.     double result ,  weight                                             ;
  19.     result = sum(0 , 64)                                                ;
  20.     weight = result / 25000                                             ;

  21.     printf("舍罕王应该给予达依尔%.0lf粒麦子!\n" , result)              ;
  22.     printf("如果每25000粒麦子为1kg,那么应该给%.0lfkg麦子!\n",weight)  ;
  23.     return 0                                                            ;
  24. }
复制代码

        编译、运行实况:
  1. D:\[00.Exercise]\[C]>g++ -o x x.c

  2. D:\[00.Exercise]\[C]>x
  3. 0 , 1 , 1
  4. 1 , 3 , 3
  5. 2 , 7 , 7
  6. 3 , 15 , 15
  7. 4 , 31 , 31
  8. 5 , 63 , 63
  9. 6 , 127 , 127
  10. 7 , 255 , 255
  11. 8 , 511 , 511
  12. 9 , 1023 , 1023
  13. 10 , 2047 , 2047
  14. 11 , 4095 , 4095
  15. 12 , 8191 , 8191
  16. 13 , 16383 , 16383
  17. 14 , 32767 , 32767
  18. 15 , 65535 , 65535
  19. 16 , 131071 , 131071
  20. 17 , 262143 , 262143
  21. 18 , 524287 , 524287
  22. 19 , 1048575 , 1048575
  23. 20 , 2097151 , 2097151
  24. 21 , 4194303 , 4194303
  25. 22 , 8388607 , 8388607
  26. 23 , 16777215 , 16777215
  27. 24 , 33554431 , 33554431
  28. 25 , 67108863 , 67108863
  29. 26 , 134217727 , 134217727
  30. 27 , 268435455 , 268435455
  31. 28 , 536870911 , 536870911
  32. 29 , 1073741823 , 1073741823
  33. 30 , 2147483647 , 2147483647
  34. 31 , 4294967295 , 4294967295
  35. 32 , 8589934591 , 8589934591
  36. 33 , 17179869183 , 17179869183
  37. 34 , 34359738367 , 34359738367
  38. 35 , 68719476735 , 68719476735
  39. 36 , 137438953471 , 137438953471
  40. 37 , 274877906943 , 274877906943
  41. 38 , 549755813887 , 549755813887
  42. 39 , 1099511627775 , 1099511627775
  43. 40 , 2199023255551 , 2199023255551
  44. 41 , 4398046511103 , 4398046511103
  45. 42 , 8796093022207 , 8796093022207
  46. 43 , 17592186044415 , 17592186044415
  47. 44 , 35184372088831 , 35184372088831
  48. 45 , 70368744177663 , 70368744177663
  49. 46 , 140737488355327 , 140737488355327
  50. 47 , 281474976710655 , 281474976710655
  51. 48 , 562949953421311 , 562949953421311
  52. 49 , 1125899906842623 , 1125899906842623
  53. 50 , 2251799813685247 , 2251799813685247
  54. 51 , 4503599627370495 , 4503599627370495
  55. 52 , 9007199254740991 , 9007199254740991
  56. 53 , 18014398509481984 , 18014398509481983               <--- 【请注意这一行】
  57. 54 , 36028797018963968 , 36028797018963967
  58. 55 , 72057594037927936 , 72057594037927935
  59. 56 , 144115188075855872 , 144115188075855871
  60. 57 , 288230376151711744 , 288230376151711743
  61. 58 , 576460752303423488 , 576460752303423487
  62. 59 , 1152921504606846976 , 1152921504606846975
  63. 60 , 2305843009213693952 , 2305843009213693951
  64. 61 , 4611686018427387904 , 4611686018427387903
  65. 62 , 9223372036854775808 , 9223372036854775807
  66. 63 , 18446744073709551616 , 18446744073709551615
  67. 舍罕王应该给予达依尔18446744073709551616粒麦子!
  68. 如果每25000粒麦子为1kg,那么应该给737869762948382kg麦子!

  69. D:\[00.Exercise]\[C]>
复制代码


        为了进一步证明是数据类型误差,本人编写了下面的代码:
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         double r1                                    ;
  5.         unsigned long long r2                        ;

  6.         r1 = 9007199254740991.0 + 9007199254740992.0 ;
  7.         r2 = 9007199254740991   + 9007199254740992   ;
  8.         printf("%.0lf , %llu\n" , r1 , r2)           ;
  9. }
复制代码

        这个代码分别用双精度浮点数和无符号长整形计算 9007199254740991 + 9007199254740992,运算结果显示,双精度浮点数的计算结果与无符号长整形数的计算结果真的不同,很显然,双精度浮点数的计算结果是错误的,这也是楼主计算结果有误差的根本原因。
        下面是编译、运行实况:
  1. D:\[00.Exercise]\[C]>g++ -o x x.c

  2. D:\[00.Exercise]\[C]>x
  3. 18014398509481984 , 18014398509481983

  4. D:\[00.Exercise]\[C]>
复制代码

         
         所以,解决方案就是,对麦粒进行计数要使用 unsigned long long,避免使用 double,下面是参考代码:
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         unsigned long long result = 0LL                                     ;
  5.         double weight                                                       ;
  6.         for(int i = 0 ; i < 64 ; i ++) result += 1LL << i                   ;
  7.         weight = result / 25000                                             ;

  8.         printf("舍罕王应该给予达依尔%llu粒麦子!\n" , result)               ;
  9.         printf("如果每25000粒麦子为1kg,那么应该给%.0lfkg麦子!\n",weight)  ;
  10.         return 0                                                            ;
  11. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2025-4-16 19:06:36 | 显示全部楼层
问题分析:
你的代码使用了 double 类型进行累加计算,但 pow(2,63) 的值超过了 double 的精确表示范围(IEEE754双精度浮点数只能精确表示2^53以内的整数),导致精度丢失,最终结果出现误差。

错误原因:
  • 当计算到棋盘第33格之后时,浮点数开始丢失精度
  • 最终计算结果是近似的 2^64 而不是准确的 2^64 -1


正确解法:
应该直接使用数学公式求和(等比数列求和公式),并改用字符串处理超大整数:


  1. #include <stdio.h>
  2. #include <math.h>

  3. // 直接使用数学公式计算结果
  4. unsigned long long calculate_grains() {
  5.     return (1ULL << 63) * 2 - 1; // 等价于 2^64 -1
  6. }

  7. int main(void) {
  8.     unsigned long long total = calculate_grains();
  9.     double weight = total / 25000.0;

  10.     printf("舍罕王应该给予达依尔%llu粒麦子!\n", total);
  11.     printf("如果每25000粒麦子为1kg,那么应该给%.0fkg麦子!\n", weight);

  12.     return 0;
  13. }
复制代码


关键说明:
  • 使用 unsigned long long 存储结果(最大支持到 18446744073709551615)
  • 通过位运算 1ULL << 63 计算准确值
  • 最终结果应为 18446744073709551615 粒(即 2^64 -1)


这个版本可以准确输出小甲鱼演示的结果,且不会出现精度丢失问题。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2025-4-17 00:07:25 | 显示全部楼层    本楼为最佳答案   
本帖最后由 jackz007 于 2025-4-17 01:00 编辑

        应该是采用 double 数据类型累加所产生的计算误差。
        请编译、运行下面的代码,这个代码会按长整型和双精度浮点数分别计算累加结果,从打印结果看,一开始,二者累加结果均一致,但是,当 i = 53 的时候,累加结果开始出现错位,双精度数的累加结果比长整型大 1。
  1. #include <math.h>

  2. double sum(int start , int end)
  3. {
  4.     double ret1 = 0.0                                                   ;
  5.     unsigned long long ret2 = 0LL                                       ;
  6.     int i = start                                                       ;

  7.     while (i < end)
  8.     {
  9.         ret1 += pow(2 , i)                                              ;
  10.         ret2 += 1LL << i                                                ;
  11.         printf("%2d , %.0lf , %llu\n" , i , ret1 , ret2)                ;
  12.         i ++                                                            ;
  13.     }
  14.     return ret1                                                         ;
  15. }

  16. int main(void)
  17. {
  18.     double result ,  weight                                             ;
  19.     result = sum(0 , 64)                                                ;
  20.     weight = result / 25000                                             ;

  21.     printf("舍罕王应该给予达依尔%.0lf粒麦子!\n" , result)              ;
  22.     printf("如果每25000粒麦子为1kg,那么应该给%.0lfkg麦子!\n",weight)  ;
  23.     return 0                                                            ;
  24. }
复制代码

        编译、运行实况:
  1. D:\[00.Exercise]\[C]>g++ -o x x.c

  2. D:\[00.Exercise]\[C]>x
  3. 0 , 1 , 1
  4. 1 , 3 , 3
  5. 2 , 7 , 7
  6. 3 , 15 , 15
  7. 4 , 31 , 31
  8. 5 , 63 , 63
  9. 6 , 127 , 127
  10. 7 , 255 , 255
  11. 8 , 511 , 511
  12. 9 , 1023 , 1023
  13. 10 , 2047 , 2047
  14. 11 , 4095 , 4095
  15. 12 , 8191 , 8191
  16. 13 , 16383 , 16383
  17. 14 , 32767 , 32767
  18. 15 , 65535 , 65535
  19. 16 , 131071 , 131071
  20. 17 , 262143 , 262143
  21. 18 , 524287 , 524287
  22. 19 , 1048575 , 1048575
  23. 20 , 2097151 , 2097151
  24. 21 , 4194303 , 4194303
  25. 22 , 8388607 , 8388607
  26. 23 , 16777215 , 16777215
  27. 24 , 33554431 , 33554431
  28. 25 , 67108863 , 67108863
  29. 26 , 134217727 , 134217727
  30. 27 , 268435455 , 268435455
  31. 28 , 536870911 , 536870911
  32. 29 , 1073741823 , 1073741823
  33. 30 , 2147483647 , 2147483647
  34. 31 , 4294967295 , 4294967295
  35. 32 , 8589934591 , 8589934591
  36. 33 , 17179869183 , 17179869183
  37. 34 , 34359738367 , 34359738367
  38. 35 , 68719476735 , 68719476735
  39. 36 , 137438953471 , 137438953471
  40. 37 , 274877906943 , 274877906943
  41. 38 , 549755813887 , 549755813887
  42. 39 , 1099511627775 , 1099511627775
  43. 40 , 2199023255551 , 2199023255551
  44. 41 , 4398046511103 , 4398046511103
  45. 42 , 8796093022207 , 8796093022207
  46. 43 , 17592186044415 , 17592186044415
  47. 44 , 35184372088831 , 35184372088831
  48. 45 , 70368744177663 , 70368744177663
  49. 46 , 140737488355327 , 140737488355327
  50. 47 , 281474976710655 , 281474976710655
  51. 48 , 562949953421311 , 562949953421311
  52. 49 , 1125899906842623 , 1125899906842623
  53. 50 , 2251799813685247 , 2251799813685247
  54. 51 , 4503599627370495 , 4503599627370495
  55. 52 , 9007199254740991 , 9007199254740991
  56. 53 , 18014398509481984 , 18014398509481983               <--- 【请注意这一行】
  57. 54 , 36028797018963968 , 36028797018963967
  58. 55 , 72057594037927936 , 72057594037927935
  59. 56 , 144115188075855872 , 144115188075855871
  60. 57 , 288230376151711744 , 288230376151711743
  61. 58 , 576460752303423488 , 576460752303423487
  62. 59 , 1152921504606846976 , 1152921504606846975
  63. 60 , 2305843009213693952 , 2305843009213693951
  64. 61 , 4611686018427387904 , 4611686018427387903
  65. 62 , 9223372036854775808 , 9223372036854775807
  66. 63 , 18446744073709551616 , 18446744073709551615
  67. 舍罕王应该给予达依尔18446744073709551616粒麦子!
  68. 如果每25000粒麦子为1kg,那么应该给737869762948382kg麦子!

  69. D:\[00.Exercise]\[C]>
复制代码


        为了进一步证明是数据类型误差,本人编写了下面的代码:
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         double r1                                    ;
  5.         unsigned long long r2                        ;

  6.         r1 = 9007199254740991.0 + 9007199254740992.0 ;
  7.         r2 = 9007199254740991   + 9007199254740992   ;
  8.         printf("%.0lf , %llu\n" , r1 , r2)           ;
  9. }
复制代码

        这个代码分别用双精度浮点数和无符号长整形计算 9007199254740991 + 9007199254740992,运算结果显示,双精度浮点数的计算结果与无符号长整形数的计算结果真的不同,很显然,双精度浮点数的计算结果是错误的,这也是楼主计算结果有误差的根本原因。
        下面是编译、运行实况:
  1. D:\[00.Exercise]\[C]>g++ -o x x.c

  2. D:\[00.Exercise]\[C]>x
  3. 18014398509481984 , 18014398509481983

  4. D:\[00.Exercise]\[C]>
复制代码

         
         所以,解决方案就是,对麦粒进行计数要使用 unsigned long long,避免使用 double,下面是参考代码:
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         unsigned long long result = 0LL                                     ;
  5.         double weight                                                       ;
  6.         for(int i = 0 ; i < 64 ; i ++) result += 1LL << i                   ;
  7.         weight = result / 25000                                             ;

  8.         printf("舍罕王应该给予达依尔%llu粒麦子!\n" , result)               ;
  9.         printf("如果每25000粒麦子为1kg,那么应该给%.0lfkg麦子!\n",weight)  ;
  10.         return 0                                                            ;
  11. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2025-4-17 08:50:35 | 显示全部楼层
double类型可以精确表示的整数范围是 -2^53 到 2^53(即 -9007199254740992 到 9007199254740992)
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-4-17 09:59:48 | 显示全部楼层
jackz007 发表于 2025-4-17 00:07
应该是采用 double 数据类型累加所产生的计算误差。
        请编译、运行下面的代码,这个代码会 ...

明白了,谢谢鱼友&#128536;
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-4-17 10:00:18 | 显示全部楼层
xiaoniugogogo 发表于 2025-4-17 08:50
double类型可以精确表示的整数范围是 -2^53 到 2^53(即 -9007199254740992 到 9007199254740992)

了解,谢谢啦
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-10 06:30

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表