1.   版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址
  2.  
  3.   http://www.cnblogs.com/Colin-Cai/p/7223254.html
  4.  
  5.   作者:窗户
  6.  
  7.   QQ6679072
  8.  
  9.   E-mail6679072@qq.com

  了解了浮点数的存储以及手算平方根的原理,我们可以考虑程序实现了。

  先实现一个64位整数的平方根,根据之前的手算平方根,程序也不是那么难写了。

  1. #include <stdint.h>
  2. uint64_t _sqrt_u64(uint64_t a)
  3. {
  4. int i;
  5. uint64_t res;
  6. uint64_t remain;
  7.  
  8. //0的平方根是0,特殊处理一下
  9. if(a == 0ull)
  10. return 0ull;
  11.  
  12. //找到最高位的1,并且产生平方根结果最高位的1
  13. for(i=62;;i-=2)
  14. if(a&(3ull<<i)) {
  15. res = 1ull;
  16. remain = ((a&(3ull<<i))>>i) - 1ull;
  17. i -= 2;
  18. break;
  19. }
  20.  
  21. //根据手算平方根的原理,依次产生各位结果
  22. for(;i>=0;i-=2) {
  23. //右移动两位,并把a接着的两位并入remain
  24. remain = (remain<<2)|((a&(3ull<<i))>>i);
  25. if(((res<<2)|1ull) <= remain) {
  26. //产生新一位的1
  27. remain = remain - ((res<<2)|1ull);
  28. res = (res<<1)|1ull;
  29. } else {
  30. //产生新一位的0
  31. res <<= 1;
  32. }
  33. }
  34.  
  35. return res;
  36. }

  其实,可以合在一起写,代码会短一些,但效率会低那么一点点,而且编译器应该不太容易优化。

  1. #include <stdint.h>
  2. uint64_t _sqrt_u64(uint64_t a)
  3. {
  4. int i;
  5. uint64_t res;
  6. uint64_t remain;
  7.  
  8. res = remain = 0ull;
  9.  
  10. for(i=62;i>=0;i-=2) {
  11. remain = (remain<<2)|((a&(3ull<<i))>>i);
  12. if(((res<<2)|1ull) <= remain) {
  13. remain = remain - ((res<<2)|1ull);
  14. res = (res<<1)|1ull;
  15. } else {
  16. res <<= 1;
  17. }
  18. }
  19.  
  20. return res;
  21. }

  不过,我们不需要这个结果。

  为了验证其正确性,我们来写个C语言的main

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. #include <inttypes.h>
  4. uint64_t _sqrt_u64(uint64_t a);
  5.  
  6. int main()
  7. {
  8. uint64_t a, b;
  9. scanf("%" PRIu64, &a);
  10. b = _sqrt_u64(a);
  11. printf("%" PRIu64 "\n",b);
  12. return 0;
  13. }

  我们shell程序测试一下,我们当然不可能测试过每一个64bits的数,这个运算量太大,不现实。我们可以用随机取一部分来测试。

  1. #!/bin/bash
  2.  
  3. #编译
  4. gcc -O2 -s sqrt_u64.c main_sqrt_u64.c -o a.out
  5.  
  6. #随机测试10000个数
  7. for((i=;i<;i++));do
  8. #随机产生bits ~,如果是0,代表测试的数就是0
  9. #如果不是0,则代表要产生的数二进制可以有多少位
  10. let bits=RANDOM%
  11. if [ $bits -eq ]; then
  12. x=
  13. y=
  14. else
  15. #产生一个bits位的二进制数x
  16. x=$({
  17. #最高位1
  18. echo -n
  19. #之后每位随机产生
  20. for((j=;j<bits;j++));do
  21. let x=RANDOM%
  22. echo -n $x
  23. done
  24. })
  25. #用bc将x转换成十进制
  26. x=$(echo 'obase=10;ibase=2;'"$x" | bc)
  27. #用bc计算x的平方根取整,理论上和我们的C语言计算一致
  28. y=$(echo 'sqrt('"$x"')' | bc)
  29. fi
  30. #z是我们的C语言计算结果
  31. z=$(echo $x | ./a.out)
  32. #比较,如果不一致,就报错
  33. if [ $y -ne $z ];then
  34. echo $x $y $z error
  35. exit
  36. fi
  37. done
  38. echo OK

  测试结果表明,我们的C语言还是可以得到正确的结果的。

  再来回忆下第一节里讲过的浮点数结构,

  S(1bits)  |   N(8bits)  |  A(23bits)

   对于浮点数a*2n,

1<=a<2,n为整数,

  如果n是偶数,

  那么a*2n的平方根是sqrt(a)*2n/2,也满足1<=sqrt(a)<2,n/2是整数;

  如果n为奇数,

  那么a*2n的平方根是sqrt(2*a)*2(n-1)/2,也满足1<=sqrt(2*a)<2,(n-1)/2是整数。

  所以此处要用a或者2*a来开平方根,

  回忆一下浮点数的结构,单精度浮点数的精度是23位。

  表示的是科学计数法a*2n的a减去1的部分,那么加上整数1可以用二进制24位表示。

  于是,我们就想,一个二进制48位或47位长的数,平方根是二进制24位。那么,我们就可以用一个48位或47位的二进制整数的平方根计算结果的小数部分。

  nan/inf/-inf以及负数的平方根都是nan,

  0.0的平方根是0.0,

  -0.0的平方根是-0.0(可能只是某些库里是这样的),

  以上都可以在计算的时候特殊化一下。

  规格数(就是用科学计数法表示的浮点数)的平方根也是规格数,

  S=0,N=0,A>0代表的是A*2-149,也就是(A*2)*2-150

  我们稍微计算一下,可以明白,所有的此类数的平方根都在规格数表示的范围内。

  于是,有了以下的代码。

  

  1. #include <stdint.h>
  2. static uint32_t _sqrt_(uint64_t a)
  3. {
  4. int i;
  5. uint64_t res;
  6. uint64_t remain;
  7.  
  8. res = remain = 0ull;
  9.  
  10. //之前整数平方根被直接优化,我们只需要求47位或者48位整数的平方根
  11. for(i=46;i>=0;i-=2) {
  12. remain = (remain<<2)|((a&(3ull<<i))>>i);
  13. if(((res<<2)|1ull) <= remain) {
  14. remain = remain - ((res<<2)|1ull);
  15. res = (res<<1)|1ull;
  16. } else {
  17. res <<= 1;
  18. }
  19. }
  20.  
  21. return (uint32_t)res;
  22. }
  23.  
  24. float mysqrtf(float f)
  25. {
  26. union {
  27. float f;
  28. uint32_t u;
  29. } n;
  30. uint32_t N,A;
  31. int _N, i;
  32. uint64_t _A;
  33.  
  34. n.f = f;
  35. if(n.u == 0x80000000 || n.u == 0x00000000) /* 0.0/-0.0 */
  36. return n.f;
  37. N = (n.u&(0xff<<23))>>23;
  38. if(N==0xff||(n.u&0x80000000)) { /* inf/-inf/nan/ f < 0.0*/
  39. n.u = 0x7fc00000; /* nan */
  40. return n.f;
  41. }
  42. if(N!=0x0) { /* 用科学计数法表示的规格数 */
  43. A = (n.u&0x7fffff)|0x800000;
  44. _N = (int)N - 127;
  45. if(N&0x1) {
  46. _A = (uint64_t)A<<23;
  47. } else {
  48. _A = (uint64_t)A<<24;
  49. _N--;
  50. }
  51. } else { //A*2^(-149)这种表示方式的浮点数
  52. //还是需要找最高位
  53. for(i=22;;i--)
  54. if(n.u&((0x1)<<i))
  55. break;
  56. //然后需要移位,要区分奇数和偶数
  57. if(i&0x1) {
  58. _N = i-149;
  59. _A = (uint64_t)n.u << (46-i);
  60. } else {
  61. _N = i-150;
  62. _A = (uint64_t)n.u << (47-i);
  63. }
  64. }
  65. //小数部分
  66. A = _sqrt_(_A);
  67. //指数部分
  68. N = (uint32_t)(_N/2+127);
  69. //得到结果
  70. n.u = (A&0x7fffff)|(N<<23);
  71. return n.f;
  72. }

  同样,也写个测试用的程序,对inf/-inf/nan/0.0/-0.0以及负数不测了,这些很简单。

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. #include <math.h>
  4. #include <time.h>
  5. #include <stdlib.h>
  6. #include <inttypes.h>
  7.  
  8. int main(int argc, char **argv)
  9. {
  10. union {
  11. float f;
  12. uint32_t u;
  13. } n;
  14. uint32_t A,N;
  15. float f,f2;
  16. int i;
  17.  
  18. srand((unsigned)time(NULL));
  19. //随机10000个数据
  20. for(i=0;i<10000;i++) {
  21. N = rand()%256;
  22. if(N==255)
  23. N=254;
  24. A = 0x0;
  25. A |= rand()%256;
  26. A |= (rand()%256)>>8;
  27. A |= (rand()%256)>>16;
  28. n.u = (A&0x7fffff)|(N<<23);
  29. f = sqrtf(n.f);
  30. f2 = mysqrtf(n.f);
  31. printf("%.60f %.60f\n",f,f2);
  32. }
  33.  
  34. return 0;
  35. }

  结果发现,我们的程序和数学库里的sqrtf结果有细微差别。

  于是,我们决定再加个小东西,就是四舍五入。之前我们用的是47位或者48位数开平方,为了四舍五入,我们需要多一位,于是就用49位或者50位数开平方。

  修改一下mysqrtf,增加两位拿去开平方,_sqrt_也动一下。

  1. #include <stdint.h>
  2. static uint32_t _sqrt_(uint64_t a)
  3. {
  4. int i;
  5. uint64_t res;
  6. uint64_t remain;
  7.  
  8. res = remain = 0ull;
  9.  
  10. //之前整数平方根被直接优化,我们只需要求49位或者50位整数的平方根
  11. for(i=48;i>=0;i-=2) {//这里之前是46,改成48
  12. remain = (remain<<2)|((a&(3ull<<i))>>i);
  13. if(((res<<2)|1ull) <= remain) {
  14. remain = remain - ((res<<2)|1ull);
  15. res = (res<<1)|1ull;
  16. } else {
  17. res <<= 1;
  18. }
  19. }
  20.  
  21. return (uint32_t)res;
  22. }
  1. float mysqrtf(float f)
  2. {
  3. union {
  4. float f;
  5. uint32_t u;
  6. } n;
  7. uint32_t N,A;
  8. int _N, i;
  9. uint64_t _A;
  10.  
  11. n.f = f;
  12. if(n.u == 0x80000000 || n.u == 0x00000000) /* 0.0/-0.0 */
  13. return n.f;
  14. N = (n.u&(0xff<<23))>>23;
  15. if(N==0xff||(n.u&0x80000000)) { /* inf/-inf/nan/ f < 0.0*/
  16. n.u = 0x7fc00000; /* nan */
  17. return n.f;
  18. }
  19. if(N!=0x0) { /* 用科学计数法表示的规格数 */
  20. A = (n.u&0x7fffff)|0x800000;
  21. _N = (int)N - 127;
  22. if(N&0x1) {
  23. _A = (uint64_t)A<<25;
  24. } else {
  25. _A = (uint64_t)A<<26;
  26. _N--;
  27. }
  28. } else { //A*2^(-149)这种表示方式的浮点数
  29. //还是需要找最高位
  30. for(i=22;;i--)
  31. if(n.u&((0x1)<<i))
  32. break;
  33. //然后需要移位,要区分奇数和偶数
  34. if(i&0x1) {
  35. _N = i-149;
  36. _A = (uint64_t)n.u << (48-i);
  37. } else {
  38. _N = i-150;
  39. _A = (uint64_t)n.u << (49-i);
  40. }
  41. }
  42. //小数部分
  43. A = _sqrt_(_A);
  44. //四舍五入
  45. A = (A+(A&0x1))>>1;
  46. //指数部分
  47. N = (uint32_t)(_N/2+127);
  48. //得到结果
  49. n.u = (A&0x7fffff)|(N<<23);
  50. return n.f;
  51. }

  然后再测,准确无误。于是我们可以完工了。

平方根的C语言实现(三) ——最终程序实现的更多相关文章

  1. 深入研究C语言 第三篇

    本篇研究TC2.0下其他几个工具.同时看看TC由源代码到exe程序的过程. 1. 用TCC将下面的程序编为.obj文件 我们知道,TCC在默认的编译连接一个C语言的源程序a.c的时候分为以下两步: ( ...

  2. 利用Scala语言开发Spark应用程序

    Spark内核是由Scala语言开发的,因此使用Scala语言开发Spark应用程序是自然而然的事情.如果你对Scala语言还不太熟悉,可 以阅读网络教程A Scala Tutorial for Ja ...

  3. .NET DLL 保护措施详解(三)最终效果

    针对.NET DLL 保护措施详解所述思路完成最终的实现,以下为程序包下载地址 下载 注意: 运行环境为.net4.0,需要安装VS2015 C++可发行组件包vc_redist.x86.exe.然后 ...

  4. ASP.NET MVC:多语言的三种技术处理策略

    ASP.NET MVC:多语言的三种技术处理策略 背景 本文介绍了多语言的三种技术处理策略,每种策略对应一种场景,这三种场景是: 多语言资源信息只被.NET使用. 多语言资源信息只被Javascrip ...

  5. Swift5 语言指南(三) 快速之旅

    传统表明,新语言中的第一个程序应在屏幕上打印“Hello,world!”字样.在Swift中,这可以在一行中完成: print("Hello, world!") // Prints ...

  6. UWP 多语言的三个概念

    首先了解一下 RFC4646 和 BCP-47 是什么东西: RFC4646 The name is a combination of an ISO 639 two-letter lowercase ...

  7. SAS进阶《深入解析SAS》之开发多语言支持的SAS程序

    SAS进阶<深入解析SAS>之开发多语言支持的SAS程序 1. 多语言支持的应用程序是指该程序在世界给第使用时,其能够处理的数据,以及处理数据的方式.信息展现的方式都符合当地的语言.文化习 ...

  8. [转载] 使用C/C++语言编写基于DSP程序的注意事项

    原文地址:『转』使用C/C++语言编写基于DSP程序的注意事项作者:skysmile   1.不影响执行速度的情况下,可以使用c或c/c++语言提供的函数库,也可以自己设计函数,这样更易于使用“裁缝师 ...

  9. C语言之简易了解程序环境

    C语言之简易了解程序环境 大纲: 程序的翻译环境 预编译 编译 汇编 链接 程序的运行环境 在ANSI C的任何一种实现中,存在两个不同的环境. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机 ...

随机推荐

  1. 11. 配置ZooKeeper ensemble

    一个ZooKeeper集群或复制的ZooKeeper服务器集群应该优化配置,以避免出现脑裂(split-brain)等情况. 由于网络分割,同一ensemble的两个不同服务器可能构成领导者不一致,因 ...

  2. 为什么大家觉得自学HTML5难?

    互联网发展到今天,越来越多的技术岗位人才出现了稀缺的状态,就拿当前的HTML5来讲,基本成为了每家互联网公司不可缺少的人才.如果抓住这个机会,把HTML5搞好,那么前途不可限量,而且这门行业是越老越吃 ...

  3. scala时间处理

    1.获取当前时间的年份.月份.天.小时等等 val nowDay=LocalDate.now().getDayOfMonth val nowDay=LocalTime.now().getHour 2. ...

  4. StringUtils工具类常用方法汇总1(判空、转换、移除、替换、反转)

      Apache commons lang3包下的StringUtils工具类中封装了一些字符串操作的方法,非常实用,使用起来也非常方便.最近自己也经常在项目中使用到了里面的一些方法,在这里将常用的方 ...

  5. 【前端开发】--js弹框

    js三种弹框 一.普通弹框 这类弹框就是仅仅是个提示作用,并不会做其它操作 关键词:alert()    这个没啥好说的,就是一个弹框.  二.判断弹框     这类框有一个判断作用 关键字:conf ...

  6. Linux EXT 文件系统 详解

    上几章我们讲到了Linux启动的一些问题,接下来我们来看一下硬盘分割和EXT格式文件系统的问题.前面提到了分区表的问题,分区表位于MBR, 占用64个字节.所谓的硬盘分区也就是对硬盘进行规划,填写分区 ...

  7. cinder块存储控制节点

    #cinder块存储控制节点 openstack pike 安装 目录汇总 http://www.cnblogs.com/elvi/p/7613861.html #cinder块存储控制节点 #在控制 ...

  8. 【LintCode·容易】用栈模拟汉诺塔问题

    用栈模拟汉诺塔问题 描述 在经典的汉诺塔问题中,有 3 个塔和 N 个可用来堆砌成塔的不同大小的盘子.要求盘子必须按照从小到大的顺序从上往下堆 (如:任意一个盘子,其必须堆在比它大的盘子上面).同时, ...

  9. JavaScript 性能优化技巧分享

    JavaScript 作为当前最为常见的直译式脚本语言,已经广泛应用于 Web 应用开发中.为了提高Web应用的性能,从 JavaScript 的性能优化方向入手,会是一个很好的选择. 本文从加载.上 ...

  10. 真正的精通Java是种什么样的境界?

    会在不适合使用java的地方不用java! 作为一名软件开发者,要追求的,应该是不断地提升自己分析问题把握事物关键点,实事求是地给出切实可行且能"一剑封喉"的优雅解决方案的能力,再 ...