Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
8.4 KiB

  1. package Math::BigFloat;
  2. use Math::BigInt;
  3. use Exporter; # just for use to be happy
  4. @ISA = (Exporter);
  5. use overload
  6. '+' => sub {new Math::BigFloat &fadd},
  7. '-' => sub {new Math::BigFloat
  8. $_[2]? fsub($_[1],${$_[0]}) : fsub(${$_[0]},$_[1])},
  9. '<=>' => sub {new Math::BigFloat
  10. $_[2]? fcmp($_[1],${$_[0]}) : fcmp(${$_[0]},$_[1])},
  11. 'cmp' => sub {new Math::BigFloat
  12. $_[2]? ($_[1] cmp ${$_[0]}) : (${$_[0]} cmp $_[1])},
  13. '*' => sub {new Math::BigFloat &fmul},
  14. '/' => sub {new Math::BigFloat
  15. $_[2]? scalar fdiv($_[1],${$_[0]}) :
  16. scalar fdiv(${$_[0]},$_[1])},
  17. 'neg' => sub {new Math::BigFloat &fneg},
  18. 'abs' => sub {new Math::BigFloat &fabs},
  19. qw(
  20. "" stringify
  21. 0+ numify) # Order of arguments unsignificant
  22. ;
  23. sub new {
  24. my ($class) = shift;
  25. my ($foo) = fnorm(shift);
  26. panic("Not a number initialized to Math::BigFloat") if $foo eq "NaN";
  27. bless \$foo, $class;
  28. }
  29. sub numify { 0 + "${$_[0]}" } # Not needed, additional overhead
  30. # comparing to direct compilation based on
  31. # stringify
  32. sub stringify {
  33. my $n = ${$_[0]};
  34. my $minus = ($n =~ s/^([+-])// && $1 eq '-');
  35. $n =~ s/E//;
  36. $n =~ s/([-+]\d+)$//;
  37. my $e = $1;
  38. my $ln = length($n);
  39. if ($e > 0) {
  40. $n .= "0" x $e . '.';
  41. } elsif (abs($e) < $ln) {
  42. substr($n, $ln + $e, 0) = '.';
  43. } else {
  44. $n = '.' . ("0" x (abs($e) - $ln)) . $n;
  45. }
  46. $n = "-$n" if $minus;
  47. # 1 while $n =~ s/(.*\d)(\d\d\d)/$1,$2/;
  48. return $n;
  49. }
  50. $div_scale = 40;
  51. # Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'.
  52. $rnd_mode = 'even';
  53. sub fadd; sub fsub; sub fmul; sub fdiv;
  54. sub fneg; sub fabs; sub fcmp;
  55. sub fround; sub ffround;
  56. sub fnorm; sub fsqrt;
  57. # Convert a number to canonical string form.
  58. # Takes something that looks like a number and converts it to
  59. # the form /^[+-]\d+E[+-]\d+$/.
  60. sub fnorm { #(string) return fnum_str
  61. local($_) = @_;
  62. s/\s+//g; # strip white space
  63. if (/^([+-]?)(\d*)(\.(\d*))?([Ee]([+-]?\d+))?$/ && "$2$4" ne '') {
  64. &norm(($1 ? "$1$2$4" : "+$2$4"),(($4 ne '') ? $6-length($4) : $6));
  65. } else {
  66. 'NaN';
  67. }
  68. }
  69. # normalize number -- for internal use
  70. sub norm { #(mantissa, exponent) return fnum_str
  71. local($_, $exp) = @_;
  72. if ($_ eq 'NaN') {
  73. 'NaN';
  74. } else {
  75. s/^([+-])0+/$1/; # strip leading zeros
  76. if (length($_) == 1) {
  77. '+0E+0';
  78. } else {
  79. $exp += length($1) if (s/(0+)$//); # strip trailing zeros
  80. sprintf("%sE%+ld", $_, $exp);
  81. }
  82. }
  83. }
  84. # negation
  85. sub fneg { #(fnum_str) return fnum_str
  86. local($_) = fnorm($_[$[]);
  87. vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0E+0'; # flip sign
  88. s/^H/N/;
  89. $_;
  90. }
  91. # absolute value
  92. sub fabs { #(fnum_str) return fnum_str
  93. local($_) = fnorm($_[$[]);
  94. s/^-/+/; # mash sign
  95. $_;
  96. }
  97. # multiplication
  98. sub fmul { #(fnum_str, fnum_str) return fnum_str
  99. local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1]));
  100. if ($x eq 'NaN' || $y eq 'NaN') {
  101. 'NaN';
  102. } else {
  103. local($xm,$xe) = split('E',$x);
  104. local($ym,$ye) = split('E',$y);
  105. &norm(Math::BigInt::bmul($xm,$ym),$xe+$ye);
  106. }
  107. }
  108. # addition
  109. sub fadd { #(fnum_str, fnum_str) return fnum_str
  110. local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1]));
  111. if ($x eq 'NaN' || $y eq 'NaN') {
  112. 'NaN';
  113. } else {
  114. local($xm,$xe) = split('E',$x);
  115. local($ym,$ye) = split('E',$y);
  116. ($xm,$xe,$ym,$ye) = ($ym,$ye,$xm,$xe) if ($xe < $ye);
  117. &norm(Math::BigInt::badd($ym,$xm.('0' x ($xe-$ye))),$ye);
  118. }
  119. }
  120. # subtraction
  121. sub fsub { #(fnum_str, fnum_str) return fnum_str
  122. fadd($_[$[],fneg($_[$[+1]));
  123. }
  124. # division
  125. # args are dividend, divisor, scale (optional)
  126. # result has at most max(scale, length(dividend), length(divisor)) digits
  127. sub fdiv #(fnum_str, fnum_str[,scale]) return fnum_str
  128. {
  129. local($x,$y,$scale) = (fnorm($_[$[]),fnorm($_[$[+1]),$_[$[+2]);
  130. if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') {
  131. 'NaN';
  132. } else {
  133. local($xm,$xe) = split('E',$x);
  134. local($ym,$ye) = split('E',$y);
  135. $scale = $div_scale if (!$scale);
  136. $scale = length($xm)-1 if (length($xm)-1 > $scale);
  137. $scale = length($ym)-1 if (length($ym)-1 > $scale);
  138. $scale = $scale + length($ym) - length($xm);
  139. &norm(&round(Math::BigInt::bdiv($xm.('0' x $scale),$ym),$ym),
  140. $xe-$ye-$scale);
  141. }
  142. }
  143. # round int $q based on fraction $r/$base using $rnd_mode
  144. sub round { #(int_str, int_str, int_str) return int_str
  145. local($q,$r,$base) = @_;
  146. if ($q eq 'NaN' || $r eq 'NaN') {
  147. 'NaN';
  148. } elsif ($rnd_mode eq 'trunc') {
  149. $q; # just truncate
  150. } else {
  151. local($cmp) = Math::BigInt::bcmp(Math::BigInt::bmul($r,'+2'),$base);
  152. if ( $cmp < 0 ||
  153. ($cmp == 0 &&
  154. ( $rnd_mode eq 'zero' ||
  155. ($rnd_mode eq '-inf' && (substr($q,$[,1) eq '+')) ||
  156. ($rnd_mode eq '+inf' && (substr($q,$[,1) eq '-')) ||
  157. ($rnd_mode eq 'even' && $q =~ /[24680]$/) ||
  158. ($rnd_mode eq 'odd' && $q =~ /[13579]$/) )) ) {
  159. $q; # round down
  160. } else {
  161. Math::BigInt::badd($q, ((substr($q,$[,1) eq '-') ? '-1' : '+1'));
  162. # round up
  163. }
  164. }
  165. }
  166. # round the mantissa of $x to $scale digits
  167. sub fround { #(fnum_str, scale) return fnum_str
  168. local($x,$scale) = (fnorm($_[$[]),$_[$[+1]);
  169. if ($x eq 'NaN' || $scale <= 0) {
  170. $x;
  171. } else {
  172. local($xm,$xe) = split('E',$x);
  173. if (length($xm)-1 <= $scale) {
  174. $x;
  175. } else {
  176. &norm(&round(substr($xm,$[,$scale+1),
  177. "+0".substr($xm,$[+$scale+1,1),"+10"),
  178. $xe+length($xm)-$scale-1);
  179. }
  180. }
  181. }
  182. # round $x at the 10 to the $scale digit place
  183. sub ffround { #(fnum_str, scale) return fnum_str
  184. local($x,$scale) = (fnorm($_[$[]),$_[$[+1]);
  185. if ($x eq 'NaN') {
  186. 'NaN';
  187. } else {
  188. local($xm,$xe) = split('E',$x);
  189. if ($xe >= $scale) {
  190. $x;
  191. } else {
  192. $xe = length($xm)+$xe-$scale;
  193. if ($xe < 1) {
  194. '+0E+0';
  195. } elsif ($xe == 1) {
  196. &norm(&round('+0',"+0".substr($xm,$[+1,1),"+10"), $scale);
  197. } else {
  198. &norm(&round(substr($xm,$[,$xe),
  199. "+0".substr($xm,$[+$xe,1),"+10"), $scale);
  200. }
  201. }
  202. }
  203. }
  204. # compare 2 values returns one of undef, <0, =0, >0
  205. # returns undef if either or both input value are not numbers
  206. sub fcmp #(fnum_str, fnum_str) return cond_code
  207. {
  208. local($x, $y) = (fnorm($_[$[]),fnorm($_[$[+1]));
  209. if ($x eq "NaN" || $y eq "NaN") {
  210. undef;
  211. } else {
  212. ord($y) <=> ord($x)
  213. ||
  214. ( local($xm,$xe,$ym,$ye) = split('E', $x."E$y"),
  215. (($xe <=> $ye) * (substr($x,$[,1).'1')
  216. || Math::BigInt::cmp($xm,$ym))
  217. );
  218. }
  219. }
  220. # square root by Newtons method.
  221. sub fsqrt { #(fnum_str[, scale]) return fnum_str
  222. local($x, $scale) = (fnorm($_[$[]), $_[$[+1]);
  223. if ($x eq 'NaN' || $x =~ /^-/) {
  224. 'NaN';
  225. } elsif ($x eq '+0E+0') {
  226. '+0E+0';
  227. } else {
  228. local($xm, $xe) = split('E',$x);
  229. $scale = $div_scale if (!$scale);
  230. $scale = length($xm)-1 if ($scale < length($xm)-1);
  231. local($gs, $guess) = (1, sprintf("1E%+d", (length($xm)+$xe-1)/2));
  232. while ($gs < 2*$scale) {
  233. $guess = fmul(fadd($guess,fdiv($x,$guess,$gs*2)),".5");
  234. $gs *= 2;
  235. }
  236. new Math::BigFloat &fround($guess, $scale);
  237. }
  238. }
  239. 1;
  240. __END__
  241. =head1 NAME
  242. Math::BigFloat - Arbitrary length float math package
  243. =head1 SYNOPSIS
  244. use Math::BigFloat;
  245. $f = Math::BigFloat->new($string);
  246. $f->fadd(NSTR) return NSTR addition
  247. $f->fsub(NSTR) return NSTR subtraction
  248. $f->fmul(NSTR) return NSTR multiplication
  249. $f->fdiv(NSTR[,SCALE]) returns NSTR division to SCALE places
  250. $f->fneg() return NSTR negation
  251. $f->fabs() return NSTR absolute value
  252. $f->fcmp(NSTR) return CODE compare undef,<0,=0,>0
  253. $f->fround(SCALE) return NSTR round to SCALE digits
  254. $f->ffround(SCALE) return NSTR round at SCALEth place
  255. $f->fnorm() return (NSTR) normalize
  256. $f->fsqrt([SCALE]) return NSTR sqrt to SCALE places
  257. =head1 DESCRIPTION
  258. All basic math operations are overloaded if you declare your big
  259. floats as
  260. $float = new Math::BigFloat "2.123123123123123123123123123123123";
  261. =over 2
  262. =item number format
  263. canonical strings have the form /[+-]\d+E[+-]\d+/ . Input values can
  264. have imbedded whitespace.
  265. =item Error returns 'NaN'
  266. An input parameter was "Not a Number" or divide by zero or sqrt of
  267. negative number.
  268. =item Division is computed to
  269. C<max($div_scale,length(dividend)+length(divisor))> digits by default.
  270. Also used for default sqrt scale.
  271. =back
  272. =head1 BUGS
  273. The current version of this module is a preliminary version of the
  274. real thing that is currently (as of perl5.002) under development.
  275. =head1 AUTHOR
  276. Mark Biggar
  277. =cut