1
0
mirror of https://github.com/MikeMcl/decimal.js.git synced 2024-09-28 22:40:48 +00:00
MikeMcl_decimal.js/test/perf/bigtime.js
Michael Mclaughlin b92f900a93 Initial commit
2014-04-02 16:28:08 +01:00

490 lines
14 KiB
JavaScript

/*
For help:
$ node bigtime -h
Usage example:
Compare the time taken by the Decimal plus method and the BigDecimal add method.
Time 10000 calls to each.
Use operands of up to 40 random digits (each unique for each iteration).
Check that the Decimal results match the BigDecimal results.
$ node bigtime plus 10000 40
*/
var arg, i, j, max, mc, method, methodIndex, precision, rounding, reps, start,
timesEqual, xs, ys, prevRss, prevHeapUsed, prevHeapTotal, showMemory,
bdM, bdT, bdR, bdRs, dM, dT, dR, dRs,
args = process.argv.splice(2),
bigdecimal = require('./lib/bigdecimal_GWT/bigdecimal'),
BigDecimal = bigdecimal.BigDecimal,
MathContext = bigdecimal.MathContext,
Decimal = require('../../decimal'),
bdMs = ['add', 'subtract', 'multiply', 'divide', 'remainder', 'compareTo', 'pow', 'negate', 'abs'],
dMs1 = ['plus', 'minus', 'times', 'dividedBy', 'modulo', 'comparedTo', 'toPower', 'negated', 'abs'],
dMs2 = ['', '', '', 'div', 'mod', 'cmp', '', 'neg', ''],
Ms = [bdMs, dMs1, dMs2],
allMs = [].concat.apply([], Ms),
bdTotal = 0,
dTotal = 0,
BD = {},
BN = {},
modes = ['UP', 'DOWN', 'CEILING', 'FLOOR', 'HALF_UP', 'HALF_DOWN', 'HALF_EVEN'],
ALWAYS_SHOW_MEMORY = false,
DEFAULT_MAX_DIGITS = 20,
DEFAULT_POW_MAX_DIGITS = 20,
DEFAULT_REPS = 1e4,
DEFAULT_POW_REPS = 1e2,
DEFAULT_PRECISION = 20,
MAX_POWER = 50,
MAX_RANDOM_EXPONENT = 100,
getRandom = function (maxDigits) {
var z,
i = 0,
// number of digits - 1
n = Math.random() * ( maxDigits || 1 ) | 0,
// No numbers between 0 and 1 or BigDecimal remainder operation may fail with 'Division impossible' error.
r = ( ( bdM == 'remainder' ? Math.random() * 9 + 1 : Math.random() * 10 ) | 0 ) + '';
//r = ( Math.random() * 10 | 0 ) + '';
if (n) {
if ( z = r === '0' ) {
r += '.';
}
for ( ; i++ < n; r += Math.random() * 10 | 0 ) {
}
// 20% chance of integer
if ( !z && Math.random() > 0.2 ) {
r = r.slice( 0, i = ( Math.random() * n | 0 ) + 1 ) + '.' + r.slice(i);
}
}
// Avoid 'division by zero' error with division and modulo.
if ((bdM == 'divide' || bdM == 'remainder') && parseFloat(r) === 0) {
r = ( ( Math.random() * 9 | 0 ) + 1 ) + '';
}
// 50% chance of negative
return Math.random() > 0.5 ? r : '-' + r;
},
/*
// Returns exponential notation.
getRandom = function (maxDigits) {
var i = 0,
// n is the number of significant digits - 1
n = Math.random() * (maxDigits || 1) | 0,
r = ( ( Math.random() * 9 | 0 ) + 1 ) + ( n ? '.' : '' );
for (; i++ < n; r += Math.random() * 10 | 0 ) {}
// Add exponent.
r += 'e' + ( Math.random() > 0.5 ? '+' : '-' ) +
( Math.random() * MAX_RANDOM_EXPONENT | 0 );
// 50% chance of being negative.
return Math.random() > 0.5 ? r : '-' + r
},
*/
getFastest = function (d, bd) {
var r;
if (Math.abs(d - bd) > 2) {
r = ((d < bd)
? 'Decimal was ' + (d ? parseFloat((bd / d).toFixed(1)) : bd)
: 'BigDecimal was ' + (bd ? parseFloat((d / bd).toFixed(1)) : d)) + ' times faster';
} else {
timesEqual = 1;
r = 'Times approximately equal';
}
return r;
},
getMemory = function (obj) {
if (showMemory) {
var mem = process.memoryUsage(),
rss = mem.rss,
heapUsed = mem.heapUsed,
heapTotal = mem.heapTotal;
if (obj) {
obj.rss += (rss - prevRss);
obj.hU += (heapUsed - prevHeapUsed);
obj.hT += (heapTotal - prevHeapTotal);
}
prevRss = rss;
prevHeapUsed = heapUsed;
prevHeapTotal = heapTotal;
}
},
toKB = function (m) { return parseFloat((m / 1024).toFixed(1)) },
getMemoryTotals = function (obj) {
return '\trss: ' + toKB(obj.rss) + '\thU: ' + toKB(obj.hU) + '\thT: ' + toKB(obj.hT);
};
arg = args[0];
if ( typeof arg != 'undefined' && !isFinite(arg) &&
allMs.indexOf(arg) == -1 && !/^-*m$/i.test(arg)) {
console.log(
'\n node bigtime [METHOD] [METHOD CALLS [MAX DIGITS [precision]]]\n' +
'\n METHOD: The method to be timed and compared with the' +
'\n \t corresponding method from BigDecimal or Decimal\n' +
'\n BigDecimal: add subtract multiply divide remainder compareTo pow' +
'\n\t\tnegate abs\n\n Decimal: plus minus times dividedBy modulo comparedTo toPower' +
'\n\t\tnegated abs (div mod cmp pow neg)' +
'\n\n METHOD CALLS: The number of method calls to be timed' +
'\n\n MAX DIGITS: The maximum number of digits of the random ' +
'\n\t\tnumbers used in the method calls\n\n ' +
'precision: The maximum number of significant digits of a result' +
'\n\t\t(The rounding mode is randomly chosen)' +
'\n\n Default values: METHOD: randomly chosen' +
'\n\t\t METHOD CALLS: ' + DEFAULT_REPS + ' (pow: ' + DEFAULT_POW_REPS + ')' +
'\n\t\t MAX DIGITS: ' + DEFAULT_MAX_DIGITS + ' (pow: ' + DEFAULT_POW_MAX_DIGITS + ')' +
'\n\t\t precision: ' + DEFAULT_PRECISION + '\n' +
'\n E.g. node bigtime\n\tnode bigtime minus\n\tnode bigtime add 100000' +
'\n\tnode bigtime times 20000 100\n\tnode bigtime div 100000 50 20' +
'\n\tnode bigtime 9000\n\tnode bigtime 1000000 20\n' +
'\n To show memory usage, include an argument m or -m' +
'\n E.g. node bigtime m add'
);
} else {
// INITALISE
Decimal.config({
toExpNeg: -9e15,
toExpPos: 9e15,
minE: -9e15,
maxE: 9e15
});
Number.prototype.toPlainString = Number.prototype.toString;
for (i = 0; i < args.length; i++) {
arg = args[i];
if (isFinite(arg)) {
arg = Math.abs(parseInt(arg));
if (reps == null) reps = arg <= 1e10 ? arg : 0;
else if (max == null) max = arg <= 1e6 ? arg : 0;
else if (precision == null) precision = arg <= 1e6 ? arg : DEFAULT_PRECISION;
} else if (/^-*m$/i.test(arg)) {
showMemory = true;
} else if (method == null) {
method = arg;
}
}
for (i = 0; i < Ms.length && (methodIndex = Ms[i].indexOf(method)) == -1; i++) {}
dM = methodIndex == -1
? dMs1[methodIndex = Math.floor(Math.random() * bdMs.length)]
: (Ms[i][0] == 'add' ? dMs1 : Ms[i])[methodIndex];
bdM = bdMs[methodIndex];
if (!reps) reps = bdM == 'pow' ? DEFAULT_POW_REPS : DEFAULT_REPS;
if (!max) max = bdM == 'pow' ? DEFAULT_POW_MAX_DIGITS : DEFAULT_MAX_DIGITS;
if (precision == null) precision = DEFAULT_PRECISION;
/*
BigDecimal remainder needs precision to be >= number of digits of operands:
"Division impossible: This occurs and signals invalid-operation if the integer result of a
divide-integer or remainder operation had too many digits (would be longer than precision)."
*/
if ( bdM == 'remainder' && max > precision ) {
max = precision;
}
// Get random rounding mode.
rounding = Math.floor(Math.random() * 7);
xs = [reps], ys = [reps], bdRs = [reps], dRs = [reps];
BD.rss = BD.hU = BD.hT = BN.rss = BN.hU = BN.hT = 0;
showMemory = showMemory || ALWAYS_SHOW_MEMORY;
console.log('\n Decimal %s vs BigDecimal %s\n' +
'\n Method calls: %d\n\n Random operands: %d', dM, bdM, reps,
bdM == 'abs' || bdM == 'negate' || bdM == 'abs' ? reps : reps * 2);
console.log(' Max. digits of operands: %d', max);
console.log('\n Precision: %d\n Rounding mode: %d', precision, rounding);
process.stdout.write('\n Testing started');
// TEST
outer:
for (; reps > 0; reps -= 1e4) {
j = Math.min(reps, 1e4);
// GENERATE RANDOM OPERANDS
for (i = 0; i < j; i++) {
xs[i] = getRandom(max);
}
if (bdM == 'pow') {
for (i = 0; i < j; i++) {
ys[i] = Math.floor(Math.random() * (MAX_POWER + 1));
}
} else if (bdM != 'abs' && bdM != 'negate') {
for (i = 0; i < j; i++) {
ys[i] = getRandom(max);
}
}
getMemory();
// BIGDECIMAL
/*
// Rounding modes 0 UP, 1 DOWN, 2 CEILING, 3 FLOOR, 4 HALF_UP, 5 HALF_DOWN, 6 HALF_EVEN
new BigDecimal('100').divide( new BigDecimal('3') ); // Exception, needs a rounding mode.
new BigDecimal('100').divide( new BigDecimal('3'), 0 ).toString(); // 34
var x = new BigDecimal('5');
var y = new BigDecimal('3');
// MathContext objects need to be initialised with a string!?
var mc = new MathContext('precision=5 roundingMode=HALF_UP');
console.log( x.divide( y, mc ).toString() ); // '1.6667'
// UNLIMITED precision=0 roundingMode=HALF_UP
// DECIMAL32 precision=7 roundingMode=HALF_EVEN
// DECIMAL64 precision=16 roundingMode=HALF_EVEN
// DECIMAL128 precision=34 roundingMode=HALF_EVEN
// Note that these are functions!
console.log( x.divide( y, MathContext.DECIMAL64() ).toString() ); // '1.666666666666667'
// Set scale (i.e. decimal places) and rounding mode.
console.log( x.divide( y, 2, 4 ).toString() ); // '1.67'
// DOWN is a function, ROUND_DOWN is not!
console.log( x.divide( y, 6, RoundingMode.DOWN() ).toString() ); // '1.666666'
console.log( x.divide( y, 6, BigDecimal.ROUND_DOWN ).toString() ); // '1.666666'
*/
mc = new MathContext('precision=' + precision + ' roundingMode=' + modes[rounding] );
if (bdM == 'pow') {
start = +new Date();
for (i = 0; i < j; i++) {
bdRs[i] = new BigDecimal(xs[i])[bdM](ys[i], mc);
}
bdT = +new Date() - start;
} else if (bdM == 'abs' || bdM == 'negate') {
start = +new Date();
for (i = 0; i < j; i++) {
bdRs[i] = new BigDecimal(xs[i])[bdM]();
}
bdT = +new Date() - start;
} else {
start = +new Date();
for (i = 0; i < j; i++) {
bdRs[i] = new BigDecimal(xs[i])[bdM](new BigDecimal(ys[i]), mc);
}
bdT = +new Date() - start;
}
getMemory(BD);
/*
// Debug: insert the following into the for-loop above.
try {
bdRs[i] = new BigDecimal(xs[i])[bdM](new BigDecimal(ys[i]), mc);
} catch(e) {
console.log(e);
console.log('\n Error. Operation number ' + i);
console.log('\n x: %s\n y: %s', xs[i], ys[i]);
console.log('\n precision: %d\n rounding: %d', precision, rounding);
bdRs[i] = { toPlainString: function () { return 'failed' } };
}
*/
// BIGNUMBER
Decimal.config({ precision: precision, rounding: rounding });
if (bdM == 'abs' || bdM == 'negate') {
start = +new Date();
for (i = 0; i < j; i++) {
dRs[i] = new Decimal(xs[i])[dM]();
}
dT = +new Date() - start;
} else {
start = +new Date();
for (i = 0; i < j; i++) {
dRs[i] = new Decimal(xs[i])[dM](ys[i]);
}
dT = +new Date() - start;
}
getMemory(BN);
// CHECK FOR MISMATCHES
for (i = 0; i < j; i++) {
dR = dRs[i].toString();
bdR = bdRs[i].toPlainString();
// Strip any trailing zeros from non-integer BigDecimals
if (bdR.indexOf('.') != -1) {
bdR = bdR.replace(/\.?0+$/, '');
}
if (bdR !== dR) {
console.log(
'\n breaking on first mismatch (result number %d):' +
'\n\n BigDecimal: %s\n Decimal: %s', i, bdR, dR
);
console.log('\n x: %s\n y: %s', xs[i], ys[i]);
console.log('\n precision: %d\n rounding: %d', precision, rounding);
break outer;
}
}
bdTotal += bdT;
dTotal += dT;
process.stdout.write(' .');
}
// TIMINGS SUMMARY
if (i == j) {
console.log(' done\n\n No mismatches.');
if (showMemory) {
console.log(
'\n Change in memory usage (KB):' +
'\n\tBigDecimal' + getMemoryTotals(BD) +
'\n\tDecimal ' + getMemoryTotals(BN)
);
}
console.log(
'\n Time taken:' + '\n\tBigDecimal ' + (bdTotal || '<1') + ' ms' +
'\n\tDecimal ' + ( dTotal || '<1') + ' ms\n\n ' +
getFastest(dTotal, bdTotal) + '\n'
);
}
}
/*
BigDecimal notes:
Java standard class library: java.math.BigDecimal
Exports:
RoundingMode
MathContext
BigDecimal
BigInteger
BigDecimal properties:
ROUND_CEILING
ROUND_DOWN
ROUND_FLOOR
ROUND_HALF_DOWN
ROUND_HALF_EVEN
ROUND_HALF_UP
ROUND_UNNECESSARY
ROUND_UP
__init__
valueOf
log
logObj
ONE
TEN
ZERO
BigDecimal instance properties/methods:
( for (var i in new BigDecimal('1').__gwt_instance.__gwtex_wrap) {...} )
byteValueExact
compareTo
doubleValue
equals
floatValue
hashCode
intValue
intValueExact
max
min
movePointLeft
movePointRight
precision
round
scale
scaleByPowerOfTen
shortValueExact
signum
stripTrailingZeros
toBigInteger
toBigIntegerExact
toEngineeringString
toPlainString
toString
ulp
unscaledValue
longValue
longValueExact
abs
add
divide
divideToIntegralValue
multiply
negate
plus
pow
remainder
setScale
subtract
divideAndRemainder
*/