Elon_Chiu 发表于 2025-4-16 19:04:02

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

#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;
}

FishC 发表于 2025-4-16 19:06:36

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

错误原因:

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


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


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

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

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

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

    return 0;
}


关键说明:

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


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

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

jackz007 发表于 2025-4-17 00:07:25

本帖最后由 jackz007 于 2025-4-17 01:00 编辑

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

double sum(int start , int end)
{
    double ret1 = 0.0                                                   ;
    unsigned long long ret2 = 0LL                                       ;
    int i = start                                                       ;

    while (i < end)
    {
      ret1 += pow(2 , i)                                              ;
      ret2 += 1LL << i                                                ;
      printf("%2d , %.0lf , %llu\n" , i , ret1 , ret2)                ;
      i ++                                                            ;
    }
    return ret1                                                         ;
}

int main(void)
{
    double result ,weight                                             ;
    result = sum(0 , 64)                                                ;
    weight = result / 25000                                             ;

    printf("舍罕王应该给予达依尔%.0lf粒麦子!\n" , result)            ;
    printf("如果每25000粒麦子为1kg,那么应该给%.0lfkg麦子!\n",weight);
    return 0                                                            ;
}
      编译、运行实况:
D:\\>g++ -o x x.c

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

D:\\>

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

int main(void)
{
      double r1                                    ;
      unsigned long long r2                        ;

      r1 = 9007199254740991.0 + 9007199254740992.0 ;
      r2 = 9007199254740991   + 9007199254740992   ;
      printf("%.0lf , %llu\n" , r1 , r2)         ;
}
      这个代码分别用双精度浮点数和无符号长整形计算 9007199254740991 + 9007199254740992,运算结果显示,双精度浮点数的计算结果与无符号长整形数的计算结果真的不同,很显然,双精度浮点数的计算结果是错误的,这也是楼主计算结果有误差的根本原因。
      下面是编译、运行实况:
D:\\>g++ -o x x.c

D:\\>x
18014398509481984 , 18014398509481983

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

int main(void)
{
      unsigned long long result = 0LL                                     ;
      double weight                                                       ;
      for(int i = 0 ; i < 64 ; i ++) result += 1LL << i                   ;
      weight = result / 25000                                             ;

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

xiaoniugogogo 发表于 2025-4-17 08:50:35

double类型可以精确表示的整数范围是 -2^53 到 2^53(即 -9007199254740992 到 9007199254740992)

Elon_Chiu 发表于 2025-4-17 09:59:48

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

明白了,谢谢鱼友&#128536;

Elon_Chiu 发表于 2025-4-17 10:00:18

xiaoniugogogo 发表于 2025-4-17 08:50
double类型可以精确表示的整数范围是 -2^53 到 2^53(即 -9007199254740992 到 9007199254740992)

了解,谢谢啦
页: [1]
查看完整版本: 为什么我的程序算出的结果比小甲鱼多一粒麦子?