本帖最后由 Croper 于 2020-1-12 14:22 编辑
恩。。。楼主说的是一个数,是不是任意有理数的平方。
那么也包括浮点数。
所以开方取整再平方的做法肯定是不对的
另一方面,平常使用的float,在python内部存储时也是使用的二进制方式。
而二进制的小数是无法正确表示大部分十进制的小数的,
比如十进制的1.1,以二进制表示,则为1.00011001100110011001...的无限循环小数。
所以,大部分float也是经过了近似处理的,
对于1.21这种,其虽然是1.1的平方,但是:
- n=1.1
- a=sqrt(n)
- b=n**0.5
- print(a**2) #等于1.2100000000000002
- print(b**2) #等于1.2100000000000002
复制代码
因为浮点数取近似的关系,也不能做到完全正确
所以,开方再平方的做法也不能取得正确的结果。
既然以上已经说到,大部分float在计算机内部并不能精确存储。
而要精确存储一个有理数,最好的方法就是使用分数,
那么这个问题就转化成了:找到一个最接近float的,(比较简单的)分数。
比如0.3333333333能划为1/3
0.142857142857能划为1/7
1.21能划为121/100
python是自带一个分数库fractions的,但是其把小数划为分数的能力有点惨不忍睹:
- from fractions import Fraction
- a=Fraction(1.21)
- print(a) #输出1362338887279575/1125899906842624
复制代码
这似乎把1.21对应的2进制近似值转化成了分数。
要解决这个问题,可以使用连分数法:
https://baike.baidu.com/item/%E8%BF%9E%E5%88%86%E6%95%B0/2715871?fr=aladdin
下面是我写的一个使用连分数法把小数划为分数的代码:
- def float2fraction(n):
- stk=[]
- for i in range(10):
- a=int(round(n))
- stk.append(a)
- if (abs(n-a)<0.00001): break
- n=1/(n-a)
- num=0
- den=1
- for i in reversed(stk):
- num,den=den,num+i*den
- num,den=den,num
- return fractions.Fraction(num,den)
复制代码
最多连分10次,并且当某一次结果的剩下的绝对值小于0.00001时停止迭代
来试一试这个函数:
- print(float2fraction(0.1)) #输出1/10
- print(float2fraction(1.21)) #输出121/100
- print(float2fraction(0.1111111111111)) #1/9
复制代码
能划为分数之后剩下的问题就简单多了,只要分子和分母都是完全平方数就说明这个有理数是另一个有理数的平方:
- def isSquareNum(n:int):
- if (isinstance(n,int)):
- return int(n**0.5)**2==n
- f=float2fraction(n)
- return isSquareNum(f.numerator) and isSquareNum(f.denominator)
复制代码
测试:
- print(isSquareNum(2)) #FALSE
- print(isSquareNum(4)) #TRUE
- print(isSquareNum(0.1)) #FALSE
- print(isSquareNum(0.01))#TRUE
- print(isSquareNum(1.44))#TRUE
- print(isSquareNum(1.444))#FALSE
复制代码
基本符合要求
当然,因为无法取得float的精确值,所以每种做法都是有缺陷的。这是我觉得缺陷比较小的做法,对于某些特定值,肯定也是会出错的。