1/3乘以3为什么不是0.999999...

本文探讨了0. 乘以3看似矛盾的数学问题,通过级数概念和计算机浮点数表示的局限性,揭示了实数与浮点数表示的差异。作者通过代码实例和实验,解释了为何在计算机中得到的结果与理论预期不符,并深入剖析了浮点数运算的精度问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有这样一个问题:\frac{1}{3}乘以3是多少?

答案毫无疑问是1。

但是你有没有想过这样一个问题:\frac{1}{3}写成无限循环小数是0.\dot{3},用0.\dot{3}乘以3,答案是0.\dot{9},对吗? 

这两个答案似乎产生了矛盾。

答案是1,这是毫无疑问的,那么问题就出在0.\dot{3}乘以3上了。

0.333333...,这是一个无限循环小数,但对于无限循环小数,我们真的完全了解它吗,在它的省略号里面,到底是什么。这里,就要引入级数的概念了。

级数,简单来说,就是一组无限数列的和。

那么,对于0.\dot{3},我们可以将其写成级数的形式: 

定义这样一个数列{a_{n}}:

a_{1}=0.3

a_{2}=0.03

a_{3}=0.003

......

a_{n}=\frac{3}{10^n}

因此,0. \dot{3}=0.3+0.03+0.003+......+\frac{3}{10^n}=a_{1}+a_{2}+a_{3}+......+ a_{n} 

令S为{a_{n}}的n项和,由等比数列求和的公式,S=\frac{1}{3}\frac{1}{10^{n}} 

n \to +\infty0.\dot{3}=\lim_{n \to +\infty }S=\lim_{n \to +\infty }\frac{1}{3}\frac{1}{10^{n}}=\frac{1}{3},很神奇地,0.\dot{3}经过运算,又变成了\frac{1}{3}

 所以,0.\dot{3}\times 3=\frac{1}{3}\times 3=1

 似乎什么都没说,是吗,但至少我们知道了0.\dot{3}如何变回\frac{1}{3},这就是意义所在。


那么在计算机中,这个问题又是怎样的呢,我立刻写了一个小儿科的代码

#include <stdio.h>

int main()
{
	double a;
	a = 1.0/3;
	a *= 3;

	printf("%.17f",a);
	
	return 0;
} 

输出结果是1.00000000000000000

嗯,没有问题。

那我们看一看a=1.0/3后a的值呢。

通过调试系统,看到a=0.33333333333333331

欸,奇怪,1.0/3怎么变成0.33333333333333331了呢,再不济也应该是0.33333333333333333啊。而且0.33333333333333331乘以3怎么也不会是1啊,这下就连级数都没法解释了。

你不能在0和1的世界里讨论0和1以外的事,我不懂。

想要弄明白这个问题,我们呢就要明白小数在计算机中是如何储存的。 

计算机是采用定点数和浮点数的形式存储小数的,具体的不再赘述,结论是:定点数表示小数没有误差,但是非常占内存且麻烦。浮点数更加灵活,但带来的问题是表示不精确,与实际的值存在误差。

我选择用来表示小数的,就是浮点数(double)

我们知道,在实数范围内,实数是稠密的,也就是说,任意挑两个数出来,无论这两个数靠的有多近,总能在这两个数之间找到其他数。

而在浮点数表示法中,数不是稠密的,而是间断的,也就是说数与数之间是有空隙的。这样就造成了实数的稠密性与浮点数的不稠密性之间的矛盾。那么,怎样表示两个间断的浮点数之间的数呢。

继续采用一些傻瓜式的调试方法寻找答案...

#include <stdio.h>

int main()
{
	double a;
	a = 1.0000000000000001/3;
	
	printf("%.17f",a);
	
	return 0;
} 

当分子为1.0000000000000001时,结果为0.33333333333333331;如果减少一个0,结果就差变成0.3333333333333337了,所以我们在前者的基础上改变末尾的数字:

#include <stdio.h>

int main()
{
	double a;
	a = 1.0000000000000002/3;

	printf("%.17f",a);
	
	return 0;
} 

结果为0.33333333333333343

好,那么我们在1.0000000000000001的后面加数字:

1.00000000000000011/3,结果还是0.33333333333333331

1.00000000000000012/3,结果变成0.33333333333333343

到现在其实已经很清楚了, 0.\dot{3} 附近的两个浮点数是0.33333333333333331和0.33333333333333343,按照就近原则,取的是更近的0.33333333333333331。

为了验证这一想法,又写了一个:

#include <stdio.h>

int main()
{
	double a = 0.33333333333333333333333;
	
	printf("%.17f",a);
	
	return 0;
} 

结果为0.33333333333333331。也就是说,无论你是真正的0 .\dot{3},是一个无限循环小数 ,还是只是一个有很多个3的有限小数,在计算机内都会按照浮点数进行存储。验证完毕。

那么至于浮点数的乘法,我就不清楚了为什么会有进位的情况发生呢。

这里再放一个伪级数(因为计算机中不能真正取到无穷,只能用0x3f3f3f替代)

#include <stdio.h>

#define inf 0x3f3f3f

int main()
{
	int n=1;
	double i;
	double sum = 0;
	for(n; n<inf ;n++)
	{
		i = 3.0/pow(10,n);
		sum += i;
	}
	
	printf("%.17f %.17f",sum,3*sum); 
	
	return 0;
}

结果为0.33333333333333326 0.99999999999999978

大概猜测一下,浮点数做乘法时是往后取的。

好啦,这篇文章就写到这里啦,只是为了自己搞着玩,去验证一下自己的猜想而已,大家就当看一乐吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值