//题目:输入一个大于3的整数n,判定它是否为素数(prime,又称质数)
#include <stdio.h>
#include <math.h>
int main()
{int n,i,k;
  printf("please enter a integer number,n=?");
  scanf("%d",&n);
  k=sqrt(n);
  for(i=2;i<=k;i++)
    if(n%i==0)break;
  if(i<=k)printf("%d is not a prime number.\n",n);
  else printf("%d is a prime number.\n",n);
    return 0;
}

  ————谭浩强.《C程序设计》(第四版).p137,清华大学出版社

  这个程序的代码首先输入需要判断的整数并存放在变量n中(其实这个变量定义为unsigned类型更理想,因为题目中已经明确是大于3的整数)。紧接着通过:

k=sqrt(n);

  试图求出n的平方根或其平方根的整数部分。然而这个写法是有问题的,能否正确地按照希望求得n的平方根或其整数部分是得不到保证的。

  这是因为sqrt()函数的函数原型是:

double sqrt (double);

  也就是说,sqrt()函数的参数和返回值都是double数据类型。double类型属于浮点数据类型的一种,浮点类型并非像整数类型那样可以精确地表示整数,正相反,浮点数据类型用于近似地表示实数,尽管在个别情况下可以精确地表示,但就一般意义上而言,浮点数据类型只是对一定范围内的实数的一种近似表示。

  因此,在k=sqrt(n)这个表达式中,不可能指望这个n是准确的,因为按照sqrt()函数原型的要求,这个n实际上表示的是(double)n,即调用时会存在类型转换,转换后的值为double类型,它是否精确等于原来的int类型的n值,一般而言是不清楚的。

  或许有些对IEEE754标准很熟悉的人对此不能赞同——他们非常清楚在这个标准下浮点数的表示方法和内部结构。好吧,“不争论”,继续看下一个不确定性。

  由于sqrt()函数的返回值是double类型,这表明sqrt()函数只承诺为我们求得实参的平方根的近似值——而不是像数学那样一定可以得到一个精确值。换句话说,当调用sqrt(9.)的时候,函数未必会精确地得到3.0这个数学上的精确的平方根,sqrt(9.)的值无论为3.000000000000001还是2.999999999999999 都有可能。这虽然是出于理性的判断,但也并非绝无经验事实作为佐证。试看下面的代码:

#include <stdio.h>
#include <math.h> int main( void )
{
int p , n = 4 ; p = pow( 10 , n ); printf("%d\n", p ); return 0;
}

  在某些编译器上的输出结果是:

9999

  这清楚地表明pow()这样的返回值为double类型的数学函数只是一种近似计算的函数。sqrt()这个数学函数也是如此,sqrt(9.)的值无论为3.0、3.000000000000001还是2.999999999999999都有可能。一旦sqrt(9.)的值为2.999999999999999,那么

k=sqrt(n);

  将使k被赋值为2而不是预期中的3。这样代码中的下一句:

for(i=2;i<=k;i++)

的循环次数就会产生错误。这种错误在何时出现很难预料,但k=sqrt(n)是一个似是而非的表达则是确切无疑的。

  这种未必立刻发作的错误比那些立刻就产生症状的BUG更可怕,它就像一颗不定时炸弹一样说不定什么时候造成损失。古代有则守株待兔的寓言,说的是某一天出现了兔子,你不能指望天天出现兔子。但这里的情形恰恰相反,尽管很多天都没出现兔子,但你保不准哪天就会突然窜出一只兔子。而一旦出现兔子,其严重后果则是你假定不会出现兔子时所无法预料的。

  那么此时应该如何求n的平方根或其整数部分呢?实际上可以利用下面的数学常识轻易求得:

1=1^2
1+3=2^2
1+3+5=3^2

1+3+5+…+(2k1)=k^2

后面正确的代码应用了这个原理。

  样本代码中的另一个问题是根本没有考虑输入一旦不小于3时应该如何处理,这样的一个严重后果是一旦用户误输入数据,比如输入了负数,程序将会发生悲惨的崩溃。在一个真正的软件中,绝对不能假设用户一定会正确地输入,否则可能带来非常严重且无法弥补的损失。

  此外,代码中的输出部分过于啰唆繁复,后面的代码对此也进行了修正。

#include <stdlib.h>

int main( void )
{
int n ; printf("请输入n的值\n");
scanf("%d",&n); if( n <= 3)
printf("输入不正确,程序退出\n");
else
{
int n_ = n , odd = 1 , k = 0 , i ;
while( n_ >= odd )
{
n_ -= odd ;
odd += 2 ;
k ++ ;
} for(i=2;i<=k;i++)
if(n%i==0)
break; printf("%d%s是素数\n" , n , (i > k)?"":"不");
}
return 0;
}

  其中:

     while( n_ >=  odd )
{
n_ -= odd ;
odd += 2 ;
k ++ ;
}

  用于求出n的平方根的整数部分。由于只需进行√n次整数的加减法运算,其效率方面也必定高于浮点运算的k=sqrt(n)。

似是而非的k=sqrt(n)的更多相关文章

  1. 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)

    在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...

  2. C语言初学者代码中的常见错误与瑕疵(3)

    问题: n-1位数字 已知w是一个大于10但不大于1000000的无符号整数,若w是n(n≥2)位的整数,则求出w的后n-1位的数. 输入: 第一行为M,表示测试数据组数. 接下来M行,每行包含一个测 ...

  3. K-means中的K值选择

    关于如何选择Kmeans等聚类算法中的聚类中心个数,主要有以下方法(译自维基): 1. 最简单的方法:K≍sqrt(N/2) 2. 拐点法:把聚类结果的F-test值(类间Variance和全局Var ...

  4. php sqrt()函数 语法

    php sqrt()函数 语法 作用:sqrt()函数的作用是对参数进行求平方根 语法:sqrt(X) 参数: 参数 描述 X 进行求平方根的数字 说明:返回将参数X进行开平方后的结果江苏大理石平台 ...

  5. Wannafly Camp 2020 Day 1I K小数查询 - 分块

    给你一个长度为\(n\)序列\(A\),有\(m\)个操作,操作分为两种: 输入\(x,y,c\),表示对\(i\in[x,y]\),令\(A_{i}=min(A_{i},c)\) 输入\(x,y,k ...

  6. 密码学笔记(5)——Rabin密码体制和语义安全性

    一.Rabin密码体制 Rabin密码体制是RSA密码体制的一种,假定模数$n=pq$不能被分解,该类体制对于选择明文攻击是计算安全的.因此,Rabin密码体制提供了一个可证明安全的密码体制的例子:假 ...

  7. UOJ #221 【NOI2016】 循环之美

    题目链接:循环之美 这道题感觉非常优美--能有一个这么优美的题面和较高的思维难度真的不容易-- 为了表示方便,让我先讲一下两个符号.\([a]\)表示如果\(a\)为真,那么返回\(1\),否则返回\ ...

  8. Sicily 1444: Prime Path(BFS)

    题意为给出两个四位素数A.B,每次只能对A的某一位数字进行修改,使它成为另一个四位的素数,问最少经过多少操作,能使A变到B.可以直接进行BFS搜索 #include<bits/stdc++.h& ...

  9. HDU 5754 Life Winner Bo 组合博弈

    Life Winner Bo Problem Description   Bo is a "Life Winner".He likes playing chessboard gam ...

随机推荐

  1. JS控制flash的播放

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xht ...

  2. Sql server中左连接语句

    数据库中学生表和课程表如下: 左连接sql语句: select a.studentName,a.studentAge,b.courseName from student a left join cou ...

  3. 使用Aspose.Cell控件实现Excel高难度报表的生成(三)

    在之前几篇文章中,介绍了关于Apsose.cell这个强大的Excel操作控件的使用,相关文章如下: 使用Aspose.Cell控件实现Excel高难度报表的生成(一) 使用Aspose.Cell控件 ...

  4. C#实现二叉查找树

    简介 树是一种非线性结构.树的本质是将一些节点由边连接起来,形成层级的结构.而二叉树是一种特殊的树,使得树每个子节点必须小于等于2.而二叉查找树又是一类特殊的二叉树.使得每一个节点的左节点或左子树的所 ...

  5. Linux命令行–基本的bash shell命令

    启动shell: /etc/passwd:包含系统用户账户列表以及每个用户的基本配置信息 每个条目有七个字段,每个字段用冒号隔开 用户名 用户密码 用户的系统UID 用户的系统GID 用户的全名 用户 ...

  6. LIB和DLL的区别与使用

    转这篇文章是因为以前有个误区,以为lib为静态库,dll为动态库,这理解并不错,但不全面.lib分两种的:(1) lib包含所有内容的静态库,应用程序用它即可,这是我理解的一种(2) lib只包含头部 ...

  7. 将hadoop源代码导入eclipse

    1. 安装JDK,eclipse,下载hadoop源代码并解压到某一个目录. 2. 安装maven,将bin目录添加到PATH环境变量中. 3. 安装protobuf2.5.0,将protoc-2.5 ...

  8. C# web api 返回类型设置为json的两种方法

    每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不 ...

  9. Servlet加载器的实验

    今天,看了张孝祥老师的类加载器的一个高级实验分析的教程,有点受益匪浅. 新建servlet工程,在Servlet类中 package com.sinosoft.servelt; import java ...

  10. display:inline-block; 到底是个啥玩意?

    display:inline; 内联元素,简单来说就是在同一行显示.display:block; 块级元素,简单来说就是就是有换行,会换到第二行.display:inline-block; 就是在同一 ...