in reply to accurately rounding numbers for percentages
You can have the quantized percentages to add to 100 but doing so will increase the quantization error compared with rounding. Doing so minimizes the aggregate error rather than the individual errors. While others have advocated minimizing the individual errors, there may be cases where minimizing the aggregate error is preferable.
The following example demonstrates one way the aggregate error can be minimized. The implementation is crude, not well tested and replete with print statements which may help you follow what it is doing.
use warnings; use strict; use Data::Dumper; my @percentages = generate(); print "@percentages\n"; my @quantized = quantize(1000,@percentages); print "Original percentages: @percentages\n"; print "Quantized percentages: @quantized\n"; my $sum; $sum += $_ foreach(@quantized);; print "Sum of quantized percentages: $sum\n"; =head2 my @quantized = quantize($factor, @percentages); The quantize() function takes a quantizaton factor and an array of percentages which should add to 100%. It returns an array of quantized percentages which does add to 100%. The percentages are quantized to multiples of (100/$factor). The function minimizes the worst case error. Two error functions are provided: one is the absolute error (the difference between the original value and the quantized value) and the other is the absolute relative error (the absolute error divided by the value being quantized). There are many other possibilities, depending on your needs. =cut sub quantize { my $quantum = 100 / shift; my $error = 0; my $sum = 0; my @x = map { my $q = sprintf("%0.0f", $_/$quantum) * $quantum; my $d = $q - $_; $error += $d; $sum += $q; [ $_, $q, $d ] } @_; print Dumper(\@x); print "initial total error: $error\n"; print "initial sum: $sum\n"; while(abs($sum - 100) > $quantum/2) { my $direction = ($sum > 100) ? 1 : -1 ; my $min_error = 10000; my $min_index = 0; print "errors of adjusted values: "; foreach my $i (0..(@x-1)) { my $e = abs($x[$i]->[2] - $quantum * $direction) / $x[$i]- +>[0]; # relative error #my $e = abs($x[$i]->[2] - $quantum * $direction); + # absolute error print " $e"; if($e < $min_error) { $min_error = $e; $min_index = $i; print "(i = $i)"; } } print "\n"; print "adjust $min_index: $x[$min_index]->[0], $x[$min_index]- +>[1] $x[$min_index]->[2]\n"; $x[$min_index]->[1] -= $quantum * $direction; $x[$min_index]->[2] -= $quantum * $direction; print "\t$x[$min_index]->[1], $x[$min_index]->[2]\n"; $sum -= $quantum * $direction; } return(map { $_->[1] } @x); } =head2 generate() The generate() function generates a somewhat random array of percentages that adds to 100%. =cut sub generate { my $sum = 0; my @percentages; foreach (1..20) { my $x = rand(50); if($sum + $x < 100) { push(@percentages, $x); $sum += $x; } } push(@percentages, 100 - $sum); return(@percentages); }
|
|---|