Upgrade to Math::BigInt 1.48.
[p5sagit/p5-mst-13.2.git] / lib / Math / BigInt / t / mbimbf.t
1 #!/usr/bin/perl -w
2
3 # test rounding, accuracy, precicion and fallback, round_mode and mixing
4 # of classes
5
6 # Make sure you always quote any bare floating-point values, lest 123.46 will
7 # be stringified to 123.4599999999 due to limited float prevision.
8
9 use strict;
10 use Test;
11
12 BEGIN 
13   {
14   $| = 1;
15   chdir 't' if -d 't';
16   unshift @INC, '../lib'; # for running manually
17   plan tests => 260;
18   }
19
20 # for finding out whether round finds correct class
21 package Foo;
22
23 use Math::BigInt;
24 use vars qw/@ISA $precision $accuracy $div_scale $round_mode/;
25 @ISA = qw/Math::BigInt/;
26
27 $precision = 6;
28 $accuracy = 8;
29 $div_scale = 5;
30 $round_mode = 'odd';
31
32 sub new
33   {
34   my $class = shift; 
35   my $self = { _a => undef, _p => undef, value => 5 };
36   bless $self, $class;
37   }
38
39 sub bstr
40   { 
41   my $self = shift;
42
43   return "$self->{value}";
44   }
45
46 # these will be called with the rounding precision or accuracy, depending on
47 # class
48 sub bround
49   {
50   my ($self,$a,$r) = @_;
51   $self->{value} = 'a' x $a;
52   return $self;
53   }
54
55 sub bnorm
56   {
57   my $self = shift;
58   return $self;
59   }
60
61 sub bfround
62   {
63   my ($self,$p,$r) = @_;
64   $self->{value} = 'p' x $p;
65   return $self;
66   }
67
68 package main;
69
70 use Math::BigInt;
71 use Math::BigFloat;
72
73 my ($x,$y,$z,$u);
74
75 ###############################################################################
76 # test defaults and set/get
77
78 ok_undef ($Math::BigInt::accuracy);
79 ok_undef ($Math::BigInt::precision);
80 ok_undef (Math::BigInt::accuracy());
81 ok_undef (Math::BigInt::precision());
82 ok_undef (Math::BigInt->accuracy());
83 ok_undef (Math::BigInt->precision());
84 ok ($Math::BigInt::div_scale,40);
85 ok (Math::BigInt::div_scale(),40);
86 ok ($Math::BigInt::round_mode,'even');
87 ok (Math::BigInt::round_mode(),'even');
88 ok (Math::BigInt->round_mode(),'even');
89
90 ok_undef ($Math::BigFloat::accuracy);
91 ok_undef ($Math::BigFloat::precision);
92 ok_undef (Math::BigFloat::accuracy());
93 ok_undef (Math::BigFloat::accuracy());
94 ok_undef (Math::BigFloat->precision());
95 ok_undef (Math::BigFloat->precision());
96 ok ($Math::BigFloat::div_scale,40);
97 ok (Math::BigFloat::div_scale(),40);
98 ok ($Math::BigFloat::round_mode,'even');
99 ok (Math::BigFloat::round_mode(),'even');
100 ok (Math::BigFloat->round_mode(),'even');
101
102 # old way
103 ok ($Math::BigInt::rnd_mode,'even');
104 ok ($Math::BigFloat::rnd_mode,'even');
105
106 $x = eval 'Math::BigInt->round_mode("huhmbi");';
107 ok ($@ =~ /^Unknown round mode huhmbi at/);
108
109 $x = eval 'Math::BigFloat->round_mode("huhmbf");';
110 ok ($@ =~ /^Unknown round mode huhmbf at/);
111
112 # old way (now with test for validity)
113 $x = eval '$Math::BigInt::rnd_mode = "huhmbi";';
114 ok ($@ =~ /^Unknown round mode huhmbi at/);
115 $x = eval '$Math::BigFloat::rnd_mode = "huhmbi";';
116 ok ($@ =~ /^Unknown round mode huhmbi at/);
117 # see if accessor also changes old variable
118 Math::BigInt->round_mode('odd');
119 ok ($Math::BigInt::rnd_mode,'odd');
120 Math::BigFloat->round_mode('odd');
121 ok ($Math::BigFloat::rnd_mode,'odd');
122
123 Math::BigInt->round_mode('even');
124 Math::BigFloat->round_mode('even');
125
126 # accessors
127 foreach my $class (qw/Math::BigInt Math::BigFloat/)
128   {
129   ok_undef ($class->accuracy());
130   ok_undef ($class->precision());
131   ok ($class->round_mode(),'even');
132   ok ($class->div_scale(),40);
133    
134   ok ($class->div_scale(20),20);
135   $class->div_scale(40); ok ($class->div_scale(),40);
136   
137   ok ($class->round_mode('odd'),'odd');
138   $class->round_mode('even'); ok ($class->round_mode(),'even');
139   
140   ok ($class->accuracy(2),2);
141   $class->accuracy(3); ok ($class->accuracy(),3);
142   ok_undef ($class->accuracy(undef));
143
144   ok ($class->precision(2),2);
145   ok ($class->precision(-2),-2);
146   $class->precision(3); ok ($class->precision(),3);
147   ok_undef ($class->precision(undef));
148   }
149
150 # accuracy
151 foreach (qw/5 42 -1 0/)
152   {
153   ok ($Math::BigFloat::accuracy = $_,$_);
154   ok ($Math::BigInt::accuracy = $_,$_);
155   }
156 ok_undef ($Math::BigFloat::accuracy = undef);
157 ok_undef ($Math::BigInt::accuracy = undef);
158
159 # precision
160 foreach (qw/5 42 -1 0/)
161   {
162   ok ($Math::BigFloat::precision = $_,$_);
163   ok ($Math::BigInt::precision = $_,$_);
164   }
165 ok_undef ($Math::BigFloat::precision = undef);
166 ok_undef ($Math::BigInt::precision = undef);
167
168 # fallback
169 foreach (qw/5 42 1/)
170   {
171   ok ($Math::BigFloat::div_scale = $_,$_);
172   ok ($Math::BigInt::div_scale = $_,$_);
173   }
174 # illegal values are possible for fallback due to no accessor
175
176 # round_mode
177 foreach (qw/odd even zero trunc +inf -inf/)
178   {
179   ok ($Math::BigFloat::round_mode = $_,$_);
180   ok ($Math::BigInt::round_mode = $_,$_);
181   }
182 $Math::BigFloat::round_mode = 'zero';
183 ok ($Math::BigFloat::round_mode,'zero');
184 ok ($Math::BigInt::round_mode,'-inf');  # from above
185
186 $Math::BigInt::accuracy = undef;
187 $Math::BigInt::precision = undef;
188 # local copies
189 $x = Math::BigFloat->new('123.456');
190 ok_undef ($x->accuracy());
191 ok ($x->accuracy(5),5);
192 ok_undef ($x->accuracy(undef),undef);
193 ok_undef ($x->precision());
194 ok ($x->precision(5),5);
195 ok_undef ($x->precision(undef),undef);
196
197 # see if MBF changes MBIs values
198 ok ($Math::BigInt::accuracy = 42,42);
199 ok ($Math::BigFloat::accuracy = 64,64);
200 ok ($Math::BigInt::accuracy,42);                # should be still 42
201 ok ($Math::BigFloat::accuracy,64);              # should be still 64
202
203 ###############################################################################
204 # see if creating a number under set A or P will round it
205
206 $Math::BigInt::accuracy = 4;
207 $Math::BigInt::precision = 3;
208
209 ok (Math::BigInt->new(123456),123500);  # with A
210 $Math::BigInt::accuracy = undef;
211 ok (Math::BigInt->new(123456),123000);  # with P
212
213 $Math::BigFloat::accuracy = 4;
214 $Math::BigFloat::precision = -1;
215 $Math::BigInt::precision = undef;
216
217 ok (Math::BigFloat->new('123.456'),'123.5');    # with A
218 $Math::BigFloat::accuracy = undef;
219 ok (Math::BigFloat->new('123.456'),'123.5');    # with P from MBF, not MBI!
220
221 $Math::BigFloat::precision = undef;
222
223 ###############################################################################
224 # see if setting accuracy/precision actually rounds the number
225
226 $x = Math::BigFloat->new('123.456'); $x->accuracy(4);   ok ($x,'123.5');
227 $x = Math::BigFloat->new('123.456'); $x->precision(-2); ok ($x,'123.46');
228
229 $x = Math::BigInt->new(123456);      $x->accuracy(4);   ok ($x,123500);
230 $x = Math::BigInt->new(123456);      $x->precision(2);  ok ($x,123500);
231
232 ###############################################################################
233 # test actual rounding via round()
234
235 $x = Math::BigFloat->new('123.456');
236 ok ($x->copy()->round(5,2),'123.46');
237 ok ($x->copy()->round(4,2),'123.5');
238 ok ($x->copy()->round(undef,-2),'123.46');
239 ok ($x->copy()->round(undef,2),100);
240
241 $x = Math::BigFloat->new('123.45000');
242 ok ($x->copy()->round(undef,-1,'odd'),'123.5');
243
244 # see if rounding is 'sticky'
245 $x = Math::BigFloat->new('123.4567');
246 $y = $x->copy()->bround();              # no-op since nowhere A or P defined
247
248 ok ($y,123.4567);                       
249 $y = $x->copy()->round(5,2);
250 ok ($y->accuracy(),5);
251 ok_undef ($y->precision());             # A has precedence, so P still unset
252 $y = $x->copy()->round(undef,2);
253 ok ($y->precision(),2);
254 ok_undef ($y->accuracy());              # P has precedence, so A still unset
255
256 # see if setting A clears P and vice versa
257 $x = Math::BigFloat->new('123.4567');
258 ok ($x,'123.4567');
259 ok ($x->accuracy(4),4);
260 ok ($x->precision(-2),-2);              # clear A
261 ok_undef ($x->accuracy());
262
263 $x = Math::BigFloat->new('123.4567');
264 ok ($x,'123.4567');
265 ok ($x->precision(-2),-2);
266 ok ($x->accuracy(4),4);                 # clear P
267 ok_undef ($x->precision());
268
269 # does copy work?
270 $x = Math::BigFloat->new(123.456); $x->accuracy(4); $x->precision(2);
271 $z = $x->copy(); ok_undef ($z->accuracy(),undef); ok ($z->precision(),2);
272
273 ###############################################################################
274 # test wether operations round properly afterwards
275 # These tests are not complete, since they do not excercise every "return"
276 # statement in the op's. But heh, it's better than nothing...
277
278 $x = Math::BigFloat->new('123.456');
279 $y = Math::BigFloat->new('654.321');
280 $x->{_a} = 5;           # $x->accuracy(5) would round $x straightaway
281 $y->{_a} = 4;           # $y->accuracy(4) would round $x straightaway
282
283 $z = $x + $y;           ok ($z,'777.8');
284 $z = $y - $x;           ok ($z,'530.9');
285 $z = $y * $x;           ok ($z,'80780');
286 $z = $x ** 2;           ok ($z,'15241');
287 $z = $x * $x;           ok ($z,'15241');
288
289 # not: $z = -$x;                ok ($z,'-123.46'); ok ($x,'123.456');
290 $z = $x->copy(); $z->{_a} = 2; $z = $z / 2; ok ($z,62);
291 $x = Math::BigFloat->new(123456); $x->{_a} = 4;
292 $z = $x->copy; $z++;    ok ($z,123500);
293
294 $x = Math::BigInt->new(123456);
295 $y = Math::BigInt->new(654321);
296 $x->{_a} = 5;           # $x->accuracy(5) would round $x straightaway
297 $y->{_a} = 4;           # $y->accuracy(4) would round $x straightaway
298
299 $z = $x + $y;           ok ($z,777800);
300 $z = $y - $x;           ok ($z,530900);
301 $z = $y * $x;           ok ($z,80780000000);
302 $z = $x ** 2;           ok ($z,15241000000);
303 # not yet: $z = -$x;            ok ($z,-123460); ok ($x,123456);
304 $z = $x->copy; $z++;    ok ($z,123460);
305 $z = $x->copy(); $z->{_a} = 2; $z = $z / 2; ok ($z,62000);
306
307 $x = Math::BigInt->new(123400); $x->{_a} = 4;
308 ok ($x->bnot(),-123400);                        # not -1234001
309
310 # both babs() and bneg() don't need to round, since the input will already
311 # be rounded (either as $x or via new($string)), and they don't change the
312 # value
313 # The two tests below peek at this by using _a illegally
314 $x = Math::BigInt->new(-123401); $x->{_a} = 4;
315 ok ($x->babs(),123401);
316 $x = Math::BigInt->new(-123401); $x->{_a} = 4;
317 ok ($x->bneg(),123401);
318
319 ###############################################################################
320 # test mixed arguments
321
322 $x = Math::BigFloat->new(10);
323 $u = Math::BigFloat->new(2.5);
324 $y = Math::BigInt->new(2);
325
326 $z = $x + $y; ok ($z,12); ok (ref($z),'Math::BigFloat');
327 $z = $x / $y; ok ($z,5); ok (ref($z),'Math::BigFloat');
328 $z = $u * $y; ok ($z,5); ok (ref($z),'Math::BigFloat');
329
330 $y = Math::BigInt->new(12345);
331 $z = $u->copy()->bmul($y,2,0,'odd'); ok ($z,31000);
332 $z = $u->copy()->bmul($y,3,0,'odd'); ok ($z,30900);
333 $z = $u->copy()->bmul($y,undef,0,'odd'); ok ($z,30863);
334 $z = $u->copy()->bmul($y,undef,1,'odd'); ok ($z,30860);
335 $z = $u->copy()->bmul($y,undef,-1,'odd'); ok ($z,30862.5);
336
337 # breakage:
338 # $z = $y->copy()->bmul($u,2,0,'odd'); ok ($z,31000);
339 # $z = $y * $u; ok ($z,5); ok (ref($z),'Math::BigInt');
340 # $z = $y + $x; ok ($z,12); ok (ref($z),'Math::BigInt');
341 # $z = $y / $x; ok ($z,0); ok (ref($z),'Math::BigInt');
342
343 ###############################################################################
344 # rounding in bdiv with fallback and already set A or P
345
346 $Math::BigFloat::accuracy = undef;
347 $Math::BigFloat::precision = undef;
348 $Math::BigFloat::div_scale = 40;
349
350 $x = Math::BigFloat->new(10); $x->{_a} = 4;
351 ok ($x->bdiv(3),'3.333');
352 ok ($x->{_a},4);                        # set's it since no fallback
353
354 $x = Math::BigFloat->new(10); $x->{_a} = 4; $y = Math::BigFloat->new(3);
355 ok ($x->bdiv($y),'3.333');
356 ok ($x->{_a},4);                        # set's it since no fallback
357
358 # rounding to P of x
359 $x = Math::BigFloat->new(10); $x->{_p} = -2;
360 ok ($x->bdiv(3),'3.33');
361
362 # round in div with requested P
363 $x = Math::BigFloat->new(10);
364 ok ($x->bdiv(3,undef,-2),'3.33');
365
366 # round in div with requested P greater than fallback
367 $Math::BigFloat::div_scale = 5;
368 $x = Math::BigFloat->new(10);
369 ok ($x->bdiv(3,undef,-8),'3.33333333');
370 $Math::BigFloat::div_scale = 40;
371
372 $x = Math::BigFloat->new(10); $y = Math::BigFloat->new(3); $y->{_a} = 4;
373 ok ($x->bdiv($y),'3.333');
374 ok ($x->{_a},4); ok ($y->{_a},4);       # set's it since no fallback
375 ok_undef ($x->{_p}); ok_undef ($y->{_p});
376
377 # rounding to P of y
378 $x = Math::BigFloat->new(10); $y = Math::BigFloat->new(3); $y->{_p} = -2;
379 ok ($x->bdiv($y),'3.33');
380 ok ($x->{_p},-2);
381  ok ($y->{_p},-2);
382 ok_undef ($x->{_a}); ok_undef ($y->{_a});
383
384 ###############################################################################
385 # test whether bround(-n) fails in MBF (undocumented in MBI)
386 eval { $x = Math::BigFloat->new(1); $x->bround(-2); };
387 ok ($@ =~ /^bround\(\) needs positive accuracy/,1);
388
389 # test whether rounding to higher accuracy is no-op
390 $x = Math::BigFloat->new(1); $x->{_a} = 4;
391 ok ($x,'1.000');
392 $x->bround(6);                  # must be no-op
393 ok ($x->{_a},4);
394 ok ($x,'1.000');
395
396 $x = Math::BigInt->new(1230); $x->{_a} = 3;
397 ok ($x,'1230');
398 $x->bround(6);                  # must be no-op
399 ok ($x->{_a},3);
400 ok ($x,'1230');
401
402 # bround(n) should set _a
403 $x->bround(2);                  # smaller works
404 ok ($x,'1200');
405 ok ($x->{_a},2);
406  
407 # bround(-n) is undocumented and only used by MBF
408 # bround(-n) should set _a
409 $x = Math::BigInt->new(12345);
410 $x->bround(-1);
411 ok ($x,'12300');
412 ok ($x->{_a},4);
413  
414 # bround(-n) should set _a
415 $x = Math::BigInt->new(12345);
416 $x->bround(-2);
417 ok ($x,'12000');
418 ok ($x->{_a},3);
419  
420 # bround(-n) should set _a
421 $x = Math::BigInt->new(12345); $x->{_a} = 5;
422 $x->bround(-3);
423 ok ($x,'10000');
424 ok ($x->{_a},2);
425  
426 # bround(-n) should set _a
427 $x = Math::BigInt->new(12345); $x->{_a} = 5;
428 $x->bround(-4);
429 ok ($x,'00000');
430 ok ($x->{_a},1);
431
432 # bround(-n) should be noop if n too big
433 $x = Math::BigInt->new(12345);
434 $x->bround(-5);
435 ok ($x,'0');                    # scale to "big" => 0
436 ok ($x->{_a},0);
437  
438 # bround(-n) should be noop if n too big
439 $x = Math::BigInt->new(54321);
440 $x->bround(-5);
441 ok ($x,'100000');               # used by MBF to round 0.0054321 at 0.0_6_00000
442 ok ($x->{_a},0);
443  
444 # bround(-n) should be noop if n too big
445 $x = Math::BigInt->new(54321); $x->{_a} = 5;
446 $x->bround(-6);
447 ok ($x,'100000');               # no-op
448 ok ($x->{_a},0);
449  
450 # bround(n) should set _a
451 $x = Math::BigInt->new(12345); $x->{_a} = 5;
452 $x->bround(5);                  # must be no-op
453 ok ($x,'12345');
454 ok ($x->{_a},5);
455  
456 # bround(n) should set _a
457 $x = Math::BigInt->new(12345); $x->{_a} = 5;
458 $x->bround(6);                  # must be no-op
459 ok ($x,'12345');
460
461 $x = Math::BigFloat->new('0.0061'); $x->bfround(-2);
462 ok ($x,'0.01');
463
464 ###############################################################################
465 # rounding with already set precision/accuracy
466
467 $x = Math::BigFloat->new(1); $x->{_p} = -5;
468 ok ($x,'1.00000');
469
470 # further rounding donw
471 ok ($x->bfround(-2),'1.00');
472 ok ($x->{_p},-2);
473
474 $x = Math::BigFloat->new(12345); $x->{_a} = 5;
475 ok ($x->bround(2),'12000');
476 ok ($x->{_a},2);
477
478 $x = Math::BigFloat->new('1.2345'); $x->{_a} = 5;
479 ok ($x->bround(2),'1.2');
480 ok ($x->{_a},2);
481
482 # mantissa/exponent format and A/P
483 $x = Math::BigFloat->new('12345.678'); $x->accuracy(4);
484 ok ($x,'12350'); ok ($x->{_a},4); ok_undef ($x->{_p});
485 ok ($x->{_m}->{_f},1); ok ($x->{_e}->{_f},1);
486 ok_undef ($x->{_m}->{_a}); ok_undef ($x->{_e}->{_a});
487 ok_undef ($x->{_m}->{_p}); ok_undef ($x->{_e}->{_p});
488
489 # check for no A/P in case of fallback
490 # result
491 $x = Math::BigFloat->new(100) / 3;
492 ok_undef ($x->{_a}); ok_undef ($x->{_p});
493
494 # result & reminder
495 $x = Math::BigFloat->new(100) / 3; ($x,$y) = $x->bdiv(3);
496 ok_undef ($x->{_a}); ok_undef ($x->{_p});
497 ok_undef ($y->{_a}); ok_undef ($y->{_p});
498
499 ###############################################################################
500 # math with two numbers with differen A and P
501
502 $x = Math::BigFloat->new(12345); $x->accuracy(4);       # '12340'
503 $y = Math::BigFloat->new(12345); $y->accuracy(2);       # '12000'
504 ok ($x+$y,24000);                               # 12340+12000=> 24340 => 24000
505
506 $x = Math::BigFloat->new(54321); $x->accuracy(4);       # '12340'
507 $y = Math::BigFloat->new(12345); $y->accuracy(3);       # '12000'
508 ok ($x-$y,42000);                               # 54320+12300=> 42020 => 42000
509
510 $x = Math::BigFloat->new('1.2345'); $x->precision(-2);  # '1.23'
511 $y = Math::BigFloat->new('1.2345'); $y->precision(-4);  # '1.2345'
512 ok ($x+$y,'2.46');                      # 1.2345+1.2300=> 2.4645 => 2.46
513
514 ###############################################################################
515 # round should find and use proper class
516
517 $x = Foo->new();
518 ok ($x->round($Foo::accuracy),'a' x $Foo::accuracy);
519 ok ($x->round(undef,$Foo::precision),'p' x $Foo::precision);
520 ok ($x->bfround($Foo::precision),'p' x $Foo::precision);
521 ok ($x->bround($Foo::accuracy),'a' x $Foo::accuracy);
522
523 ###############################################################################
524 # find out whether _find_round_parameters is doing what's it's supposed to do
525  
526 $Math::BigInt::accuracy = undef;
527 $Math::BigInt::precision = undef;
528 $Math::BigInt::div_scale = 40;
529 $Math::BigInt::round_mode = 'odd';
530  
531 $x = Math::BigInt->new(123);
532 my @params = $x->_find_round_parameters();
533 ok (scalar @params,1);                          # nothing to round
534
535 @params = $x->_find_round_parameters(1);
536 ok (scalar @params,4);                          # a=1
537 ok ($params[0],$x);                             # self
538 ok ($params[1],1);                              # a
539 ok_undef ($params[2]);                          # p
540 ok ($params[3],'odd');                          # round_mode
541
542 @params = $x->_find_round_parameters(undef,2);
543 ok (scalar @params,4);                          # p=2
544 ok ($params[0],$x);                             # self
545 ok_undef ($params[1]);                          # a
546 ok ($params[2],2);                              # p
547 ok ($params[3],'odd');                          # round_mode
548
549 eval { @params = $x->_find_round_parameters(undef,2,'foo'); };
550 ok ($@ =~ /^Unknown round mode 'foo'/,1);
551
552 @params = $x->_find_round_parameters(undef,2,'+inf');
553 ok (scalar @params,4);                          # p=2
554 ok ($params[0],$x);                             # self
555 ok_undef ($params[1]);                          # a
556 ok ($params[2],2);                              # p
557 ok ($params[3],'+inf');                         # round_mode
558
559 @params = $x->_find_round_parameters(2,-2,'+inf');
560 ok (scalar @params,4);                          # p=2
561 ok ($params[0],$x);                             # self
562 ok ($params[1],2);                              # a
563 ok ($params[2],-2);                             # p
564 ok ($params[3],'+inf');                         # round_mode
565
566 # all done
567
568 ###############################################################################
569 # Perl 5.005 does not like ok ($x,undef)
570
571 sub ok_undef
572   {
573   my $x = shift;
574
575   ok (1,1) and return if !defined $x;
576   ok ($x,'undef');
577   }
578