nextPerfectSquare
这是一题摘自 codewars上的训练题。
Write a function name nextPerfectSquare / next_perfect_square that returns the first perfect square that is greater than its integer argument. A perfect square is a integer that is equal to some integer squared. For example 16 is a perfect square because 16=4*4.
Caution! The largest number test is close to Int64.MaxValue
题目很简单,就是给定一个数字,求出其后第一个平方数。负数因为没有平方数,其后第一个平方数为0。
examle
n next prefect square
6 9
36 49
0 1
-5 1
题目很简单,因为很多人都会想到,将 n n n开平方,开方后的结果向下取整,其下数字再平方即为所求结果。我想信没有人会使用循环,从 n + 1 n+1 n+1开始,然后测试开方,得到整数即为下一平方数。
根据上文的叙述,差不多所有人都会考虑先开方的方式进行处理。那么程序也非常简单可以实现:
public static long NextPerfectSquare(long n)
{
if(n<0) return 0;
return (long)Math.Pow((long)Math.Sqrt(n+1),2);
}
严格来说没有问题的,Math.Sqrt(n+1)
与Math.Sqrt(n)+1
的区别不大,都是解决若给字的数字是一个平方数时情况。这个问题解决起来也是三五分钟的事儿。运行也会通过测试,看起来天衣无缝,也几乎没有人注意到其中会存在的问题。
我的思路也是如此,先将 n n n开平方后,将其平方根取整叠1后的平方数即是要求的值。但是我看到其Caution时多了一重的想法——Math.Sqrt(long)
时不一定能得到正确的结果!
在使用Math.Sqrt我们必须知道,该方法的入参是一个double数据类型,该类型是64bit空间,而long类型也是64bit空间。事实上每个计算机从业人员都知道double使用的是阶码表示法,而long使用的是被码表示法。换句话来说,同样64bit空间内,我们肯定用一个double表示出所有long,也就是说这步转换存在一个值不精确的问题。那么直接使用Maht.Sqrt肯定会存在较大的bug!
double阶码表示
double类型的有效数字只有52bits,而其余的空间是阶数与符号的存储。换句话来说,其0~51是表示有效位数,而52-62则是阶数表示位置,63表示的是符号。如果不考虑阶码的情况下,其值只能表示为 0 0 0至 2 51 2^{51} 251!而long不考虑符号位时表示的能力却是 0 0 0至 2 63 − 1 2^{63}-1 263−1,即然存在这个差别,那也就是说Math.Sqrt入参为double的情况下,使用long计算未必是正确的。
当然,这里涉及到一个隐式计算,就是long可以隐式转换double。因为double虽然使用是仍是64bit的空间,但因为阶码的存在,它下可表示 2 10 2^{10} 210个小数点移位!也就是说可以左移小数点或右移小数点约 1024 / 10 ∗ 3 = 308 1024/1