mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
1632 lines
43 KiB
Plaintext
1632 lines
43 KiB
Plaintext
/*
|
|
* CPDecimal.j
|
|
* Foundation
|
|
*
|
|
* Created by Stephen Paul Ierodiaconou
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/*
|
|
Ported From GNUStep :
|
|
|
|
NSDecimal functions
|
|
Copyright (C) 2000 Free Software Foundation, Inc.
|
|
|
|
Written by: Fred Kiefer <FredKiefer@gmx.de>
|
|
Created: July 2000
|
|
|
|
This file is part of the GNUstep Base Library.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02111 USA.
|
|
|
|
<title>NSDecimal class reference</title>
|
|
$Date: 2008-06-12 04:44:00 -0600 (Thu, 12 Jun 2008) $ $Revision: 26630 $
|
|
*/
|
|
|
|
@import "CPArray.j"
|
|
@import "CPNumber.j"
|
|
|
|
@typedef CPDecimal
|
|
|
|
// Decimal size limits
|
|
CPDecimalMaxDigits = 38;
|
|
CPDecimalMaxExponent = 127;
|
|
CPDecimalMinExponent = -128;
|
|
|
|
// Scale for no Rounding
|
|
CPDecimalNoScale = 128
|
|
|
|
// CPCalculationError enum
|
|
CPCalculationNoError = 0;
|
|
CPCalculationLossOfPrecision = 1;
|
|
CPCalculationOverflow = 2;
|
|
CPCalculationUnderflow = 3;
|
|
CPCalculationDivideByZero = 4;
|
|
|
|
//CPRoundingMode Enum
|
|
@typedef CPRoundingMode
|
|
CPRoundPlain = 1;
|
|
CPRoundDown = 2;
|
|
CPRoundUp = 3;
|
|
CPRoundBankers = 4;
|
|
_CPRoundHalfDown = 5; // Private API rounding mode used by CPNumberFormatter.
|
|
|
|
//Exceptions
|
|
CPDecimalNumberOverflowException = @"CPDecimalNumberOverflowException";
|
|
CPDecimalNumberUnderflowException = @"CPDecimalNumberUnderflowException";
|
|
CPDecimalNumberExactnessException = @"CPDecimalNumberExactnessException";
|
|
CPDecimalNumberDivideByZeroException = @"CPDecimalNumberDivideByZeroException";
|
|
|
|
/*
|
|
Initialisers for NSDecimal do not exist so here I have created my own.
|
|
The coefficient is called the 'mantissa' in this implementation as this is what Cocoa calls it.
|
|
|
|
CPDecimal format:
|
|
._mantissa : CPArray, containing each digit of the number as an unsigned integer.
|
|
._exponent : integer, the exponent of the number as an signed integer.
|
|
._isNegative : BOOL, sign of number
|
|
._isCompact : BOOL, has number been compacted.
|
|
._isNaN : BOOL, is NaN (Not a number) i.e. number is invalid.
|
|
*/
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Creates a CPDecimal object from a string representation of the decimal number.
|
|
@param decimalString CPString of number
|
|
@param roundingMode Rounding mode for when number is too large to fit in mantissa.
|
|
@return A CPDecimal object, or nil on error.
|
|
*/
|
|
// FIXME: locale support and Cocoaify, needs to accept .1 and leading 0s
|
|
function CPDecimalMakeWithString(string, locale)
|
|
{
|
|
if (!string)
|
|
return CPDecimalMakeNaN();
|
|
|
|
// Regexp solution as found in JSON spec, with working regexp (I added groupings)
|
|
// Test here: http://www.regexplanet.com/simple/index.html
|
|
// Info from: http://stackoverflow.com/questions/638565/parsing-scientific-notation-sensibly
|
|
// ([+\-]?)((?:0|[0-9]\d*)) - integer part, can have leading zeros (follows Cocoa behaviour)
|
|
// (?:\.(\d*))? - optional decimal part plus number in group
|
|
// (?:[eE]([+\-]?)(\d+))? - optional exponent part plus number in group
|
|
// group 0: string, 1: sign, 2: integer, 3: decimal, 4: exponent sign, 5: exponent
|
|
|
|
// Note: this doesn't accept .01 for example, should it?
|
|
// If yes simply add '?' after integer part group, i.e. ([+\-]?)((?:0|[1-9]\d*)?)
|
|
// Note: now it accept .01 style.
|
|
var matches = string.match(new RegExp("^([+\\-]?)((?:0|[0-9]\\d*)?)(?:\\.(\\d*))?(?:[eE]([+\\-]?)(\\d+))?$"));
|
|
|
|
if (!matches)
|
|
return CPDecimalMakeNaN();
|
|
|
|
var ds = matches[1],
|
|
intpart = matches[2],
|
|
decpart = matches[3],
|
|
es = matches[4],
|
|
exp = matches[5];
|
|
|
|
var isNegative = NO;
|
|
|
|
if (ds && ds === "-")
|
|
isNegative = YES;
|
|
|
|
var exponent = 0;
|
|
|
|
if (exp)
|
|
exponent = parseInt(exp) * ((es && es === "-")?-1:1);
|
|
|
|
if (decpart) // push decimal point to last digit, then let compact handle the zeros
|
|
exponent -= decpart.length;
|
|
|
|
var inputlength = (intpart?intpart.length:0) + (decpart?decpart.length:0);
|
|
|
|
if (inputlength > CPDecimalMaxDigits)
|
|
{
|
|
// input is too long, increase exponent and truncate
|
|
exponent += inputlength - CPDecimalMaxDigits;
|
|
}
|
|
else if (inputlength === 0)
|
|
{
|
|
return CPDecimalMakeNaN();
|
|
}
|
|
|
|
if (exponent > CPDecimalMaxExponent || exponent < CPDecimalMinExponent)
|
|
return CPDecimalMakeNaN();
|
|
|
|
// Representation internally starts at most significant digit
|
|
var m = [],
|
|
i = 0;
|
|
|
|
for (; i < (intpart?intpart.length:0); i++)
|
|
{
|
|
if (i >= CPDecimalMaxDigits)
|
|
break; // truncate
|
|
Array.prototype.push.call(m, parseInt(intpart.charAt(i)));
|
|
}
|
|
|
|
var j = 0;
|
|
|
|
for (; j < (decpart?decpart.length:0); j++)
|
|
{
|
|
if ((i + j) >= CPDecimalMaxDigits)
|
|
break; // truncate
|
|
|
|
Array.prototype.push.call(m, parseInt(decpart.charAt(j)));
|
|
}
|
|
|
|
var dcm = {_exponent:exponent, _isNegative:isNegative, _isCompact:NO, _isNaN:NO, _mantissa:m};
|
|
CPDecimalCompact(dcm);
|
|
|
|
return dcm;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Creates a CPDecimal object from a given mantissa and exponent. The sign is taken from the sign of the mantissa. This cant do a full 34 digit mantissa representation as JS's 32 bits or 64bit binary FP numbers cant represent that. So use the CPDecimalMakeWithString if you want longer mantissa.
|
|
@param mantissa the mantissa (though see above note)
|
|
@param exponent the exponent
|
|
@return A CPDecimal object, or nil on error.
|
|
*/
|
|
function CPDecimalMakeWithParts(mantissa, exponent)
|
|
{
|
|
var m = [],
|
|
isNegative = NO;
|
|
|
|
if (mantissa < 0)
|
|
{
|
|
isNegative = YES;
|
|
mantissa = ABS(mantissa);
|
|
}
|
|
|
|
if (mantissa == 0)
|
|
Array.prototype.push.call(m, 0);
|
|
|
|
if (exponent > CPDecimalMaxExponent || exponent < CPDecimalMinExponent)
|
|
return CPDecimalMakeNaN();
|
|
|
|
// remaining digits are disposed of via truncation
|
|
while ((mantissa > 0) && (m.length < CPDecimalMaxDigits))
|
|
{
|
|
Array.prototype.unshift.call(m, parseInt(mantissa % 10));
|
|
mantissa = FLOOR(mantissa / 10);
|
|
}
|
|
|
|
var dcm = {_exponent:exponent, _isNegative:isNegative, _isCompact:YES, _isNaN:NO, _mantissa:m};
|
|
|
|
CPDecimalCompact(dcm);
|
|
|
|
return dcm;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Creates a CPDecimal 0.
|
|
@return A CPDecimal object containing the value 0.
|
|
*/
|
|
function CPDecimalMakeZero()
|
|
{
|
|
return CPDecimalMakeWithParts(0, 0);
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Creates a CPDecimal 1.
|
|
@return A CPDecimal object containing the value 1.
|
|
*/
|
|
function CPDecimalMakeOne()
|
|
{
|
|
return CPDecimalMakeWithParts(1, 0);
|
|
}
|
|
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Creates a CPDecimal NaN.
|
|
@return A CPDecimal object containing the value NaN
|
|
*/
|
|
function CPDecimalMakeNaN()
|
|
{
|
|
var d = CPDecimalMakeWithParts(0, 0);
|
|
d._isNaN = YES;
|
|
return d;
|
|
}
|
|
|
|
// private methods
|
|
function _CPDecimalMakeMaximum()
|
|
{
|
|
var s = @"",
|
|
i = 0;
|
|
for (; i < CPDecimalMaxDigits; i++)
|
|
s += "9";
|
|
s += "e" + CPDecimalMaxExponent;
|
|
return CPDecimalMakeWithString(s);
|
|
}
|
|
|
|
function _CPDecimalMakeMinimum()
|
|
{
|
|
var s = @"-",
|
|
i = 0;
|
|
for (; i < CPDecimalMaxDigits; i++)
|
|
s += "9";
|
|
s += "e" + CPDecimalMaxExponent;
|
|
return CPDecimalMakeWithString(s);
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Checks to see if a CPDecimal is zero. Can handle uncompacted strings.
|
|
@return TRUE on zero.
|
|
*/
|
|
function CPDecimalIsZero(dcm)
|
|
{
|
|
// exponent doesn't matter as long as mantissa = 0
|
|
if (!dcm._isNaN)
|
|
{
|
|
for (var i = 0; i < dcm._mantissa.length; i++)
|
|
if (dcm._mantissa[i] !== 0)
|
|
return NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Checks to see if a CPDecimal is 1. Can handle uncompacted strings (it compacts them).
|
|
@return TRUE on 1.
|
|
*/
|
|
function CPDecimalIsOne(dcm)
|
|
{
|
|
CPDecimalCompact(dcm);
|
|
|
|
// exponent doesn't matter as long as mantissa = 0
|
|
if (!dcm._isNaN)
|
|
{
|
|
if (dcm._mantissa && (dcm._mantissa.length == 1) && (dcm._mantissa[0] == 1))
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
//private method to copy attribute values
|
|
function _CPDecimalSet(t, s)
|
|
{
|
|
// should all be [x copy] ?
|
|
t._exponent = s._exponent;
|
|
t._isNegative = s._isNegative;
|
|
t._isCompact = s._isCompact;
|
|
t._isNaN = s._isNaN;
|
|
t._mantissa = Array.prototype.slice.call(s._mantissa, 0);
|
|
}
|
|
|
|
function _CPDecimalSetZero(result)
|
|
{
|
|
result._mantissa = [0];
|
|
result._exponent = 0;
|
|
result._isNegative = NO;
|
|
result._isCompact = YES;
|
|
result._isNaN = NO;
|
|
}
|
|
|
|
function _CPDecimalSetOne(result)
|
|
{
|
|
result._mantissa = [1];
|
|
result._exponent = 0;
|
|
result._isNegative = NO;
|
|
result._isCompact = YES;
|
|
result._isNaN = NO;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Checks to see if a CPDecimal is Not A Number.
|
|
@return TRUE on NaN.
|
|
*/
|
|
function CPDecimalIsNotANumber(dcm)
|
|
{
|
|
return (dcm._isNaN)?YES:NO;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Create a copy of a CPDecimal object
|
|
@param dcm the CPDecimal number to copy.
|
|
@return a new CPDecimal.
|
|
*/
|
|
function CPDecimalCopy(dcm)
|
|
{
|
|
return {_exponent:dcm._exponent,
|
|
_isNegative:dcm._isNegative,
|
|
_isCompact:dcm._isCompact,
|
|
_isNaN:dcm._isNaN,
|
|
_mantissa:Array.prototype.slice.call(dcm._mantissa, 0)
|
|
};
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Compare two CPDecimal objects. Order is left to right (i.e. Ascending would
|
|
mean left is smaller than right operand).
|
|
@param leftOperand the left CPDecimal
|
|
@param rightOperand the right CPDecimal
|
|
@return CPOrderedAscending, CPOrderedDescending or CPOrderedSame.
|
|
*/
|
|
function CPDecimalCompare(leftOperand, rightOperand)
|
|
{
|
|
if (leftOperand._isNaN && rightOperand._isNaN)
|
|
return CPOrderedSame;
|
|
|
|
if (leftOperand._isNegative != rightOperand._isNegative)
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedDescending;
|
|
else
|
|
return CPOrderedAscending;
|
|
}
|
|
|
|
// Before comparing number size check if zero (dont use CPDecimalIsZero as it is more computationally intensive)
|
|
var leftIsZero = (leftOperand._mantissa.length == 1 && leftOperand._mantissa[0] == 0),
|
|
rightIsZero = (rightOperand._mantissa.length == 1 && rightOperand._mantissa[0] == 0),
|
|
// Sign is the same, quick check size (length + exp)
|
|
s1 = leftOperand._exponent + leftOperand._mantissa.length,
|
|
s2 = rightOperand._exponent + rightOperand._mantissa.length;
|
|
|
|
if (leftIsZero && rightIsZero)
|
|
return CPOrderedSame;
|
|
|
|
if (leftIsZero || (s1 < s2 && !rightIsZero))
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedDescending;
|
|
else
|
|
return CPOrderedAscending;
|
|
}
|
|
if (rightIsZero || (s1 > s2 && !leftIsZero))
|
|
{
|
|
if (leftOperand._isNegative)
|
|
return CPOrderedAscending;
|
|
else
|
|
return CPOrderedDescending;
|
|
}
|
|
|
|
// Same size, so check mantissa
|
|
var l = MIN(leftOperand._mantissa.length, rightOperand._mantissa.length),
|
|
i = 0;
|
|
|
|
for (; i < l; i++)
|
|
{
|
|
var d = rightOperand._mantissa[i] - leftOperand._mantissa[i];
|
|
|
|
if (d > 0)
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedDescending;
|
|
else
|
|
return CPOrderedAscending;
|
|
}
|
|
if (d < 0)
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedAscending;
|
|
else
|
|
return CPOrderedDescending;
|
|
}
|
|
}
|
|
|
|
// Same digits, check length
|
|
if (leftOperand._mantissa.length > rightOperand._mantissa.length)
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedAscending;
|
|
else
|
|
return CPOrderedDescending;
|
|
}
|
|
if (leftOperand._mantissa.length < rightOperand._mantissa.length)
|
|
{
|
|
if (rightOperand._isNegative)
|
|
return CPOrderedDescending;
|
|
else
|
|
return CPOrderedAscending;
|
|
}
|
|
|
|
return CPOrderedSame;
|
|
}
|
|
|
|
// GNUSteps addition. This is standard O(n) complexity.
|
|
// longMode makes the addition not round for up to double max digits, this is
|
|
// to preserve precision during multiplication
|
|
function _SimpleAdd(result, leftOperand, rightOperand, roundingMode, longMode)
|
|
{
|
|
var factor = (longMode)?2:1;
|
|
|
|
_CPDecimalSet(result, leftOperand);
|
|
|
|
var j = leftOperand._mantissa.length - rightOperand._mantissa.length,
|
|
l = rightOperand._mantissa.length,
|
|
i = l - 1,
|
|
carry = 0,
|
|
error = CPCalculationNoError;
|
|
|
|
// Add all the digits
|
|
for (; i >= 0; i--)
|
|
{
|
|
var d = rightOperand._mantissa[i] + result._mantissa[i + j] + carry;
|
|
if (d >= 10)
|
|
{
|
|
d = d % 10; // a division. subtraction and conditions faster?
|
|
carry = 1;
|
|
}
|
|
else
|
|
carry = 0;
|
|
|
|
result._mantissa[i + j] = d;
|
|
}
|
|
|
|
if (carry)
|
|
{
|
|
for (i = j - 1; i >= 0; i--)
|
|
{
|
|
if (result._mantissa[i] != 9)
|
|
{
|
|
result._mantissa[i]++;
|
|
carry = 0;
|
|
break;
|
|
}
|
|
result._mantissa[i] = 0;
|
|
}
|
|
|
|
if (carry)
|
|
{
|
|
Array.prototype.splice.call(result._mantissa, 0, 0, 1);
|
|
|
|
// The number must be shifted to the right
|
|
if ((CPDecimalMaxDigits * factor) == leftOperand._mantissa.length)
|
|
{
|
|
var scale = - result._exponent - 1;
|
|
CPDecimalRound(result, result, scale, roundingMode);
|
|
}
|
|
|
|
if (CPDecimalMaxExponent < result._exponent)
|
|
{
|
|
result._isNaN = YES;
|
|
error = CPCalculationOverflow;
|
|
result._exponent = CPDecimalMaxExponent;
|
|
}
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Performs the addition of 2 CPDecimal numbers.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param leftOperand the left CPDecimal operand
|
|
@param rightOperand the right CPDecimal operand
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalAdd(result, leftOperand, rightOperand, roundingMode, longMode)
|
|
{
|
|
if (leftOperand._isNaN || rightOperand._isNaN)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
// check for zero
|
|
if (CPDecimalIsZero(leftOperand))
|
|
{
|
|
_CPDecimalSet(result, rightOperand);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
if (CPDecimalIsZero(rightOperand))
|
|
{
|
|
_CPDecimalSet(result, leftOperand);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
var n1 = CPDecimalCopy(leftOperand),
|
|
n2 = CPDecimalCopy(rightOperand);
|
|
|
|
// For different signs use subtraction
|
|
if (leftOperand._isNegative != rightOperand._isNegative)
|
|
{
|
|
if (leftOperand._isNegative)
|
|
{
|
|
n1._isNegative = NO;
|
|
return CPDecimalSubtract(result, rightOperand, n1, roundingMode);
|
|
}
|
|
else
|
|
{
|
|
n2._isNegative = NO;
|
|
return CPDecimalSubtract(result, leftOperand, n2, roundingMode);
|
|
}
|
|
}
|
|
|
|
var normerror = CPDecimalNormalize(n1, n2, roundingMode, longMode);
|
|
|
|
// below is equiv. of simple compare
|
|
var comp = 0,
|
|
ll = n1._mantissa.length,
|
|
lr = n2._mantissa.length;
|
|
|
|
if (ll == lr)
|
|
comp = CPOrderedSame;
|
|
else if (ll > lr)
|
|
comp = CPOrderedDescending;
|
|
else
|
|
comp = CPOrderedAscending;
|
|
|
|
// both negative, make positive
|
|
if (leftOperand._isNegative)
|
|
{
|
|
n1._isNegative = NO;
|
|
n2._isNegative = NO;
|
|
|
|
// SimpleCompare does not look at sign
|
|
if (comp == CPOrderedDescending)
|
|
{
|
|
adderror = _SimpleAdd(result, n1, n2, roundingMode, longMode);
|
|
}
|
|
else
|
|
{
|
|
adderror = _SimpleAdd(result, n2, n1, roundingMode, longMode);
|
|
}
|
|
|
|
result._isNegative = YES;
|
|
|
|
// swap sign over over/underflow exception
|
|
if (CPCalculationUnderflow == adderror)
|
|
adderror = CPCalculationOverflow;
|
|
else if (CPCalculationUnderflow == adderror)
|
|
adderror = CPCalculationUnderflow;
|
|
}
|
|
else
|
|
{
|
|
if (comp == CPOrderedAscending)
|
|
{
|
|
adderror = _SimpleAdd(result, n2, n1, roundingMode, longMode);
|
|
}
|
|
else
|
|
{
|
|
adderror = _SimpleAdd(result, n1, n2, roundingMode, longMode);
|
|
}
|
|
}
|
|
|
|
CPDecimalCompact(result);
|
|
|
|
if (adderror == CPCalculationNoError)
|
|
return normerror;
|
|
else
|
|
return adderror;
|
|
}
|
|
|
|
// GNUStep port internal subtract
|
|
function _SimpleSubtract(result, leftOperand, rightOperand, roundingMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
borrow = 0,
|
|
l = rightOperand._mantissa.length,
|
|
j = leftOperand._mantissa.length - l,
|
|
i = l - 1;
|
|
|
|
_CPDecimalSet(result, leftOperand);
|
|
|
|
// Now subtract all digits
|
|
for (; i >= 0; i--)
|
|
{
|
|
var d = result._mantissa[i + j] - rightOperand._mantissa[i] - borrow;
|
|
|
|
if (d < 0)
|
|
{
|
|
d = d + 10;
|
|
borrow = 1;
|
|
}
|
|
else
|
|
borrow = 0;
|
|
|
|
result._mantissa[i + j] = d;
|
|
}
|
|
|
|
if (borrow)
|
|
{
|
|
for (i = j - 1; i >= 0; i--)
|
|
{
|
|
if (result._mantissa[i] != 0)
|
|
{
|
|
result._mantissa[i]--;
|
|
break;
|
|
}
|
|
result._mantissa[i] = 9;
|
|
}
|
|
|
|
if (-1 == i)
|
|
{
|
|
error = nil;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Performs the subtraction of 2 CPDecimal numbers.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param leftOperand the left CPDecimal operand
|
|
@param rightOperand the right CPDecimal operand
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalSubtract(result, leftOperand, rightOperand, roundingMode)
|
|
{
|
|
if (leftOperand._isNaN || rightOperand._isNaN)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
// check for zero
|
|
if (CPDecimalIsZero(leftOperand))
|
|
{
|
|
_CPDecimalSet(result, rightOperand);
|
|
result._isNegative = !result._isNegative;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
if (CPDecimalIsZero(rightOperand))
|
|
{
|
|
_CPDecimalSet(result, leftOperand);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
var n1 = CPDecimalCopy(leftOperand),
|
|
n2 = CPDecimalCopy(rightOperand),
|
|
error1 = CPCalculationNoError;
|
|
|
|
// For different signs use addition
|
|
if (leftOperand._isNegative != rightOperand._isNegative)
|
|
{
|
|
if (leftOperand._isNegative)
|
|
{
|
|
n1._isNegative = NO;
|
|
error1 = CPDecimalAdd(result, n1, rightOperand, roundingMode);
|
|
result._isNegative = YES;
|
|
|
|
if (error1 == CPCalculationUnderflow)
|
|
error1 = CPCalculationOverflow;
|
|
else if (error1 == CPCalculationOverflow) // gnustep has bug here
|
|
error1 = CPCalculationUnderflow;
|
|
|
|
return error1;
|
|
}
|
|
else
|
|
{
|
|
n2._isNegative = NO;
|
|
return CPDecimalAdd(result, leftOperand, n2, roundingMode);
|
|
}
|
|
}
|
|
|
|
var error = CPDecimalNormalize(n1, n2, roundingMode),
|
|
comp = CPDecimalCompare(leftOperand, rightOperand);
|
|
|
|
if (comp == CPOrderedSame)
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
// both negative, make positive and change order
|
|
if (leftOperand._isNegative)
|
|
{
|
|
n1._isNegative = NO;
|
|
n2._isNegative = NO;
|
|
|
|
if (comp == CPOrderedAscending)
|
|
{
|
|
error1 = _SimpleSubtract(result, n1, n2, roundingMode);
|
|
result._isNegative = YES;
|
|
}
|
|
else
|
|
{
|
|
error1 = _SimpleSubtract(result, n2, n1, roundingMode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (comp == CPOrderedAscending)
|
|
{
|
|
error1 = _SimpleSubtract(result, n2, n1, roundingMode);
|
|
result._isNegative = YES;
|
|
}
|
|
else
|
|
{
|
|
error1 = _SimpleSubtract(result, n1, n2, roundingMode);
|
|
}
|
|
}
|
|
|
|
CPDecimalCompact(result);
|
|
|
|
if (error1 == CPCalculationNoError)
|
|
return error;
|
|
else
|
|
return error1;
|
|
}
|
|
|
|
// this is a very simple O(n^2) implementation that uses subtract. Are there faster divides?
|
|
function _SimpleDivide(result, leftOperand, rightOperand, roundingMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
n1 = CPDecimalMakeZero(),
|
|
k = 0,
|
|
firsttime = YES,
|
|
stopk = CPDecimalMaxDigits + 1,
|
|
used = 0; // How many digits of l have been used?
|
|
|
|
_CPDecimalSetZero(result);
|
|
|
|
n1._mantissa = [];
|
|
|
|
while ((used < leftOperand._mantissa.length) || (n1._mantissa.length
|
|
&& !((n1._mantissa.length == 1) && (n1._mantissa[0] == 0))))
|
|
{
|
|
while (CPOrderedAscending == CPDecimalCompare(n1, rightOperand))
|
|
{
|
|
if (stopk == k)
|
|
break;
|
|
|
|
if (n1._exponent)
|
|
{
|
|
// Put back zeros removed by compacting
|
|
Array.prototype.push.call(n1._mantissa, 0);
|
|
n1._exponent--;
|
|
n1._isCompact = NO;
|
|
}
|
|
else
|
|
{
|
|
if (used < leftOperand._mantissa.length)
|
|
{
|
|
// Fill up with own digits
|
|
if (n1._mantissa.length || leftOperand._mantissa[used])
|
|
{
|
|
// only add 0 if there is already something
|
|
Array.prototype.push.call(n1._mantissa, (leftOperand._mantissa[used]));
|
|
n1._isCompact = NO;
|
|
}
|
|
|
|
used++;
|
|
}
|
|
else
|
|
{
|
|
if (result._exponent == CPDecimalMinExponent)
|
|
{
|
|
// use this as an end flag
|
|
k = stopk;
|
|
break;
|
|
}
|
|
|
|
// Borrow one digit
|
|
Array.prototype.push.call(n1._mantissa, 0);
|
|
result._exponent--;
|
|
}
|
|
|
|
// Zeros must be added while enough digits are fetched to do the
|
|
// subtraction, but first time round this just add zeros at the
|
|
// start of the number , increases k, and hence reduces
|
|
// the available precision. To solve this only inc k/add zeros if
|
|
// this isn't first time round.
|
|
if (!firsttime)
|
|
{
|
|
k++;
|
|
result._mantissa[k - 1] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point digit in result we are working on is (k-1) so when
|
|
// k == (CPDecimalMaxDigits+1) then we should stop i.e. last subtract
|
|
// was last valid one.
|
|
if (stopk == k)
|
|
{
|
|
error = CPCalculationLossOfPrecision;
|
|
break;
|
|
}
|
|
|
|
if (firsttime)
|
|
{
|
|
firsttime = NO;
|
|
k++;
|
|
}
|
|
|
|
error1 = CPDecimalSubtract(n1, n1, rightOperand, roundingMode);
|
|
|
|
if (error1 != CPCalculationNoError)
|
|
error = error1;
|
|
|
|
result._mantissa[k - 1]++;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Performs a division of 2 CPDecimal numbers.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param leftOperand the left CPDecimal operand
|
|
@param rightOperand the right CPDecimal operand
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalDivide(result, leftOperand, rightOperand, roundingMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
exp = leftOperand._exponent - rightOperand._exponent,
|
|
neg = (leftOperand._isNegative != rightOperand._isNegative);
|
|
|
|
if (leftOperand._isNaN || rightOperand._isNaN)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
// check for zero
|
|
if (CPDecimalIsZero(rightOperand))
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationDivideByZero;
|
|
}
|
|
|
|
if (CPDecimalIsZero(leftOperand))
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
//FIXME: Should also check for one
|
|
|
|
var n1 = CPDecimalCopy(leftOperand),
|
|
n2 = CPDecimalCopy(rightOperand);
|
|
|
|
n1._exponent = 0;
|
|
n1._isNegative = NO;
|
|
n2._exponent = 0;
|
|
n2._isNegative = NO;
|
|
|
|
error = _SimpleDivide(result, n1, n2, roundingMode);
|
|
CPDecimalCompact(result);
|
|
|
|
if (result._exponent + exp > CPDecimalMaxExponent)
|
|
{
|
|
result._isNaN = YES;
|
|
if (neg)
|
|
return CPCalculationUnderflow;
|
|
else
|
|
return CPCalculationOverflow;
|
|
}
|
|
else if (result._exponent + exp < CPDecimalMinExponent)
|
|
{
|
|
// We must cut off some digits
|
|
CPDecimalRound(result, result, exp + CPDecimalMaxExponent + 1, roundingMode);
|
|
error = CPCalculationLossOfPrecision;
|
|
|
|
if (result._exponent + exp < CPDecimalMinExponent)
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
result._exponent += exp;
|
|
result._isNegative = neg;
|
|
return error;
|
|
}
|
|
|
|
// Simple multiply O(n^2) , replace with something faster, like divide-n-conquer algo?
|
|
function _SimpleMultiply(result, leftOperand, rightOperand, roundingMode, powerMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
carry = 0,
|
|
exp = 0,
|
|
n = CPDecimalMakeZero();
|
|
|
|
_CPDecimalSetZero(result);
|
|
|
|
// Do every digit of the second number
|
|
for (var i = 0; i < rightOperand._mantissa.length; i++)
|
|
{
|
|
_CPDecimalSetZero(n);
|
|
|
|
n._exponent = rightOperand._mantissa.length - i - 1;
|
|
carry = 0;
|
|
d = rightOperand._mantissa[i];
|
|
|
|
if (d == 0)
|
|
continue;
|
|
|
|
for (var j = leftOperand._mantissa.length - 1; j >= 0; j--)
|
|
{
|
|
e = leftOperand._mantissa[j] * d + carry;
|
|
|
|
if (e >= 10)
|
|
{
|
|
carry = FLOOR(e / 10);
|
|
e = e % 10;
|
|
}
|
|
else
|
|
carry = 0;
|
|
|
|
// This is one off to allow final carry
|
|
n._mantissa[j + 1] = e;
|
|
}
|
|
|
|
n._mantissa[0] = carry;
|
|
|
|
CPDecimalCompact(n);
|
|
|
|
error1 = CPDecimalAdd(result, result, n, roundingMode, YES);
|
|
|
|
if (error1 != CPCalculationNoError)
|
|
error = error1;
|
|
}
|
|
|
|
if (result._exponent + exp > CPDecimalMaxExponent)
|
|
{
|
|
// This should almost never happen
|
|
result._isNaN = YES;
|
|
return CPCalculationOverflow;
|
|
}
|
|
|
|
result._exponent += exp;
|
|
|
|
// perform round to CPDecimalMaxDigits
|
|
if (result._mantissa.length > CPDecimalMaxDigits && !powerMode)
|
|
{
|
|
result._isCompact = NO;
|
|
|
|
var scale = CPDecimalMaxDigits - (result._mantissa.length + result._exponent);
|
|
CPDecimalRound(result, result, scale, roundingMode); // calls compact
|
|
|
|
error = CPCalculationLossOfPrecision;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Performs multiplication of 2 CPDecimal numbers.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param leftOperand the left CPDecimal operand
|
|
@param rightOperand the right CPDecimal operand
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalMultiply(result, leftOperand, rightOperand, roundingMode, powerMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
exp = leftOperand._exponent + rightOperand._exponent,
|
|
neg = (leftOperand._isNegative != rightOperand._isNegative);
|
|
|
|
if (leftOperand._isNaN || rightOperand._isNaN)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
// check for zero
|
|
if (CPDecimalIsZero(rightOperand) || CPDecimalIsZero(leftOperand))
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
//FIXME: Should also check for one
|
|
|
|
if (exp > CPDecimalMaxExponent)
|
|
{
|
|
result._isNaN = YES;
|
|
|
|
if (neg)
|
|
return CPCalculationUnderflow;
|
|
else
|
|
return CPCalculationOverflow;
|
|
}
|
|
|
|
var n1 = CPDecimalCopy(leftOperand),
|
|
n2 = CPDecimalCopy(rightOperand);
|
|
|
|
n1._exponent = 0;
|
|
n2._exponent = 0;
|
|
n1._isNegative = NO;
|
|
n2._isNegative = NO;
|
|
|
|
// below is equiv. of simple compare
|
|
var comp = 0,
|
|
ll = n1._mantissa.length,
|
|
lr = n2._mantissa.length;
|
|
|
|
if (ll == lr)
|
|
comp = CPOrderedSame;
|
|
else if (ll > lr)
|
|
comp = CPOrderedDescending;
|
|
else
|
|
comp = CPOrderedAscending;
|
|
|
|
if (comp == CPOrderedDescending)
|
|
{
|
|
error = _SimpleMultiply(result, n1, n2, roundingMode, powerMode);
|
|
}
|
|
else
|
|
{
|
|
error = _SimpleMultiply(result, n2, n1, roundingMode, powerMode);
|
|
}
|
|
|
|
CPDecimalCompact(result);
|
|
|
|
if (result._exponent + exp > CPDecimalMaxExponent)
|
|
{
|
|
result._isNaN = YES;
|
|
|
|
if (neg)
|
|
return CPCalculationUnderflow;
|
|
else
|
|
return CPCalculationOverflow;
|
|
}
|
|
else if (result._exponent + exp < CPDecimalMinExponent)
|
|
{
|
|
// We must cut off some digits
|
|
CPDecimalRound(result, result, exp + CPDecimalMaxExponent + 1, roundingMode);
|
|
error = CPCalculationLossOfPrecision;
|
|
|
|
if (result._exponent + exp < CPDecimalMinExponent)
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
result._exponent += exp;
|
|
result._isNegative = neg;
|
|
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Raises a CPDecimal number to a power of 10.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param dcm the CPDecimal operand
|
|
@param power the power to raise to
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalMultiplyByPowerOf10(result, dcm, power, roundingMode)
|
|
{
|
|
_CPDecimalSet(result, dcm);
|
|
|
|
var p = result._exponent + power;
|
|
|
|
if (p > CPDecimalMaxExponent)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationOverflow;
|
|
}
|
|
|
|
if (p < CPDecimalMinExponent)
|
|
{
|
|
result._isNaN = YES;
|
|
return CPCalculationUnderflow;
|
|
}
|
|
|
|
result._exponent += power;
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Raises a CPDecimal number to the given power.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param dcm the CPDecimal operand
|
|
@param power the power to raise to
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalPower(result, dcm, power, roundingMode)
|
|
{
|
|
var error = CPCalculationNoError,
|
|
neg = (dcm._isNegative && (power % 2)),
|
|
n1 = CPDecimalCopy(dcm);
|
|
|
|
n1._isNegative = NO;
|
|
|
|
_CPDecimalSetOne(result);
|
|
|
|
var e = power;
|
|
|
|
while (e)
|
|
{
|
|
if (e & 1)
|
|
{
|
|
error = CPDecimalMultiply(result, result, n1, roundingMode); //, YES); // enable for high precision powers
|
|
}
|
|
|
|
error = CPDecimalMultiply(n1, n1, n1, roundingMode); //, YES); // enable for high precision powers
|
|
|
|
e >>= 1;
|
|
|
|
if (error > CPCalculationLossOfPrecision)
|
|
break;
|
|
}
|
|
|
|
result._isNegative = neg;
|
|
|
|
/* // enable is powerMode to do finally rounding to Max Digits.
|
|
if ([result._mantissa count] > CPDecimalMaxDigits)
|
|
{
|
|
result._isCompact = NO;
|
|
var scale = CPDecimalMaxDigits - ([result._mantissa count] + result._exponent);
|
|
CPDecimalRound(result, result, scale ,roundingMode); // calls compact
|
|
error = CPCalculationLossOfPrecision;
|
|
}
|
|
*/
|
|
|
|
CPDecimalCompact(result);
|
|
|
|
return error;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Normalises 2 CPDecimals. Normalisation is the process of modifying a
|
|
numbers mantissa to ensure that both CPDecimals have the same exponent.
|
|
@param dcm1 the first CPDecimal
|
|
@param dcm2 the second CPDecimal
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalNormalize(dcm1, dcm2, roundingMode, longMode)
|
|
{
|
|
var factor = (longMode) ? 2 : 1;
|
|
|
|
if (dcm1._isNaN || dcm2._isNaN)
|
|
return CPCalculationNoError; // FIXME: correct behavior?
|
|
|
|
// ensure compact
|
|
if (!dcm1._isCompact)
|
|
CPDecimalCompact(dcm1);
|
|
|
|
if (!dcm2._isCompact)
|
|
CPDecimalCompact(dcm2);
|
|
|
|
if (dcm1._exponent == dcm2._exponent)
|
|
return CPCalculationNoError;
|
|
|
|
var e1 = dcm1._exponent,
|
|
e2 = dcm2._exponent;
|
|
|
|
// Add zeros
|
|
var l2 = dcm2._mantissa.length,
|
|
l1 = dcm1._mantissa.length,
|
|
l = 0;
|
|
|
|
var e = 0;
|
|
|
|
if (e2 > e1 && e1 >= 0 && e2 >= 0)
|
|
e = e2 - e1;
|
|
else if (e2 > e1 && e1 < 0 && e2 >= 0)
|
|
e = e2 - e1;
|
|
else if (e2 > e1 && e1 < 0 && e2 < 0)
|
|
e = e2 - e1;
|
|
else if (e2 < e1 && e1 >= 0 && e2 >= 0)
|
|
e = e1 - e2;
|
|
else if (e2 < e1 && e1 >= 0 && e2 < 0)
|
|
e = e1 - e2;
|
|
else if (e2 < e1 && e1 < 0 && e2 < 0)
|
|
e = e1 - e2;
|
|
|
|
if (e2 > e1)
|
|
l = MIN((CPDecimalMaxDigits * factor) - l2, e); //(e2 - e1));
|
|
else
|
|
l = MIN((CPDecimalMaxDigits * factor) - l1, e); //(e1 - e2));
|
|
|
|
for (var i = 0; i < l; i++)
|
|
{
|
|
if (e2 > e1)
|
|
Array.prototype.push.call(dcm2._mantissa, 0); //dcm2._mantissa[i + l2] = 0;
|
|
else
|
|
Array.prototype.push.call(dcm1._mantissa, 0);
|
|
}
|
|
|
|
if (e2 > e1)
|
|
{
|
|
dcm2._exponent -= l;
|
|
dcm2._isCompact = NO;
|
|
}
|
|
else
|
|
{
|
|
dcm1._exponent -= l;
|
|
dcm1._isCompact = NO;
|
|
}
|
|
|
|
// has been normalised?
|
|
if (l != ABS(e2 - e1))//e2 - e1)
|
|
{
|
|
// no..
|
|
// Round of some digits to increase exponent - will compact too
|
|
// One number may become zero after this
|
|
if (e2 > e1)
|
|
{
|
|
CPDecimalRound(dcm1, dcm1, -dcm2._exponent, roundingMode);
|
|
l1 = CPDecimalIsZero(dcm1);
|
|
}
|
|
else
|
|
{
|
|
CPDecimalRound(dcm2, dcm2, -dcm1._exponent, roundingMode);
|
|
l2 = CPDecimalIsZero(dcm2);
|
|
}
|
|
|
|
if ((dcm1._exponent != dcm2._exponent) && ((!l1) || (!l2)))
|
|
{
|
|
// Some zeros where cut of again by compacting
|
|
if (e2 > e1)
|
|
{
|
|
l1 = dcm1._mantissa.length;
|
|
l = MIN((CPDecimalMaxDigits * factor) - l1, ABS(dcm1._exponent - dcm2._exponent));
|
|
for (var i = 0; i < l; i++)
|
|
{
|
|
dcm1._mantissa[i + l1] = 0; // or addObject: ? one faster than other?
|
|
}
|
|
dcm1._isCompact = NO;
|
|
dcm1._exponent = dcm2._exponent;
|
|
}
|
|
else
|
|
{
|
|
l2 = dcm2._mantissa.length;
|
|
l = MIN((CPDecimalMaxDigits * factor) - l2, ABS(dcm2._exponent - dcm1._exponent));
|
|
for (var i = 0; i < l; i++)
|
|
{
|
|
dcm2._mantissa[i + l2] = 0; // or addObject: ? one faster than other?
|
|
}
|
|
dcm2._exponent = dcm1._exponent;
|
|
dcm2._isCompact = NO;
|
|
}
|
|
}
|
|
|
|
return CPCalculationLossOfPrecision;
|
|
}
|
|
|
|
return CPCalculationNoError;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Rounds a CPDecimal off at a given decimal position. scale specifies the
|
|
position. Negative values of scale imply rounding in the whole numbers and
|
|
positive values rounding in the decimal places. A scale of 0 rounds to
|
|
the first whole number.
|
|
@param result the CPDecimal object in which to put the result
|
|
@param dcm the CPDecimal operand
|
|
@param scale the position to round to
|
|
@param roundingMode the rounding mode for the operation
|
|
@return a CPCalculationError status value.
|
|
*/
|
|
function CPDecimalRound(result, dcm, scale, roundingMode)
|
|
{
|
|
_CPDecimalSet(result, dcm);
|
|
|
|
if (dcm._isNaN)
|
|
return;
|
|
|
|
if (!dcm._isCompact)
|
|
CPDecimalCompact(dcm);
|
|
|
|
// FIXME: check for valid inputs (eg scale etc)
|
|
|
|
// FIXME: if in longMode should this double?
|
|
if (scale == CPDecimalNoScale)
|
|
return;
|
|
|
|
var mc = result._mantissa.length,
|
|
l = mc + scale + result._exponent;
|
|
|
|
if (mc <= l)
|
|
return;
|
|
|
|
else if (l <= 0)
|
|
{
|
|
_CPDecimalSetZero(result);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
var c = 0,
|
|
n = 0,
|
|
up = 0;
|
|
|
|
// Adjust length and exponent
|
|
result._exponent += mc - l;
|
|
|
|
switch (roundingMode)
|
|
{
|
|
case CPRoundDown:
|
|
up = result._isNegative;
|
|
break;
|
|
|
|
case CPRoundUp:
|
|
up = !result._isNegative;
|
|
break;
|
|
|
|
case CPRoundPlain:
|
|
n = result._mantissa[l];
|
|
up = (n >= 5);
|
|
break;
|
|
|
|
case _CPRoundHalfDown:
|
|
n = result._mantissa[l];
|
|
up = (n > 5);
|
|
break;
|
|
|
|
case CPRoundBankers:
|
|
n = result._mantissa[l];
|
|
|
|
if (n > 5)
|
|
up = YES;
|
|
else if (n < 5)
|
|
up = NO;
|
|
else
|
|
{
|
|
if (l == 0)
|
|
c = 0;
|
|
else
|
|
c = result._mantissa[l - 1];
|
|
up = ((c % 2) != 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
up = NO;
|
|
break;
|
|
}
|
|
|
|
// cut mantissa
|
|
result._mantissa = Array.prototype.slice.call(result._mantissa, 0, l);
|
|
|
|
if (up)
|
|
{
|
|
for (var i = l-1; i >= 0; i--)
|
|
{
|
|
if (result._mantissa[i] != 9)
|
|
{
|
|
result._mantissa[i]++;
|
|
break;
|
|
}
|
|
|
|
result._mantissa[i] = 0;
|
|
}
|
|
|
|
// Final overflow?
|
|
if (i == -1)
|
|
{
|
|
// As all digits are zeros, just change the first
|
|
result._mantissa[0] = 1;
|
|
|
|
if (result._exponent >= CPDecimalMaxExponent)
|
|
{
|
|
// Overflow in rounding.
|
|
// Add one zero add the end. There must be space as
|
|
// we just cut off some digits.
|
|
Array.prototype.push.call(result._mantissa, 0);
|
|
}
|
|
else
|
|
result._exponent++;
|
|
}
|
|
}
|
|
}
|
|
|
|
CPDecimalCompact(result);
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Remove trailing and leading zeros from mantissa.
|
|
@param dcm the CPDecimal operand
|
|
*/
|
|
function CPDecimalCompact(dcm)
|
|
{
|
|
// if positive or zero exp leading zeros simply delete, trailing ones u need to increment exponent
|
|
if (!dcm || dcm._mantissa.length == 0 || CPDecimalIsNotANumber(dcm))
|
|
return;
|
|
|
|
if (CPDecimalIsZero(dcm))
|
|
{
|
|
// handle zero number compacting
|
|
_CPDecimalSetZero(dcm);
|
|
return;
|
|
}
|
|
|
|
// leading zeros, when exponent is zero these mean we need to move our decimal point to compact
|
|
// if exp is zero does it make sense to have them? don't think so so delete them
|
|
while (dcm._mantissa[0] === 0)
|
|
Array.prototype.shift.call(dcm._mantissa);
|
|
|
|
// trailing zeros, strip them
|
|
while (dcm._mantissa[dcm._mantissa.length - 1] === 0)
|
|
{
|
|
Array.prototype.pop.call(dcm._mantissa);
|
|
dcm._exponent++;
|
|
|
|
if (dcm._exponent + 1 > CPDecimalMaxExponent)
|
|
{
|
|
// TODO: test case for this
|
|
// overflow if we compact anymore, so don't
|
|
break;
|
|
}
|
|
}
|
|
|
|
dcm._isCompact = YES;
|
|
}
|
|
|
|
/*!
|
|
@ingroup foundation
|
|
Convert a CPDecimal to a string representation.
|
|
@param dcm the CPDecimal operand
|
|
@param locale the locale to use for the conversion
|
|
@return a CPString
|
|
*/
|
|
function CPDecimalString(dcm, locale)
|
|
{
|
|
// Cocoa seems to just add all the zeros... this maybe controlled by locale,
|
|
// will check.
|
|
if (dcm._isNaN)
|
|
return @"NaN";
|
|
|
|
var string = @"",
|
|
i = 0;
|
|
|
|
if (dcm._isNegative)
|
|
string += "-";
|
|
|
|
var k = dcm._mantissa.length,
|
|
l = ((dcm._exponent < 0) ? dcm._exponent : 0) + k;
|
|
|
|
if (l < 0)
|
|
{
|
|
// add leading zeros
|
|
string += "0.";
|
|
for (i = 0; i < ABS(l); i++)
|
|
{
|
|
string += "0";
|
|
}
|
|
l = k;
|
|
}
|
|
else if (l == 0)
|
|
{
|
|
string += "0";
|
|
}
|
|
|
|
for (i = 0; i < l; i++)
|
|
{
|
|
string += dcm._mantissa[i];
|
|
}
|
|
|
|
if (l < k)
|
|
{
|
|
string += ".";
|
|
for (i = l; i < k; i++)
|
|
{
|
|
string += dcm._mantissa[i];
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < dcm._exponent; i++)
|
|
{
|
|
string += "0";
|
|
}
|
|
|
|
return string;
|
|
/*
|
|
// GNUStep
|
|
if (dcm._isNaN)
|
|
return @"NaN";
|
|
|
|
var sep = 0;
|
|
if ((locale == nil) || (sep = [locale objectForKey: CPDecimalSeparator]) == nil)
|
|
sep = @".";
|
|
|
|
if (CPDecimalIsZero(dcm))
|
|
return @"0" + sep + "0";
|
|
|
|
var string = @"";
|
|
|
|
if (dcm._isNegative)
|
|
string += "-";
|
|
|
|
var len = [dcm._mantissa count],
|
|
size = len + dcm._exponent;
|
|
|
|
if ((len <= 6) && (0 < size) && (size < 7))
|
|
{
|
|
// For small numbers use the normal format
|
|
var i = 0
|
|
for (; i < len; i++)
|
|
{
|
|
if (size == i)
|
|
string += sep;
|
|
d = dcm._mantissa[i];
|
|
string += d.toString();
|
|
}
|
|
for (i = 0; i < dcm._exponent; i++)
|
|
{
|
|
string += "0";
|
|
}
|
|
}
|
|
else if ((len <= 6) && (0 >= size) && (size > -3))
|
|
{
|
|
// For small numbers use the normal format
|
|
string += "0";
|
|
string += sep;
|
|
|
|
var i = 0;
|
|
for (; i > size; i--)
|
|
{
|
|
string += "0";
|
|
}
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
d = dcm._mantissa[i];
|
|
string += d.toString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Scientific format
|
|
var i = 0;
|
|
for (; i < len; i++)
|
|
{
|
|
if (1 == i)
|
|
string += sep;
|
|
d = dcm._mantissa[i];
|
|
string += d.toString();
|
|
}
|
|
if (size != 1)
|
|
{
|
|
//s = [NSString stringWithFormat: @"E%d", size-1];
|
|
//[string appendString: s];
|
|
string += "E" + (size - 1).toString();
|
|
}
|
|
}
|
|
|
|
return string;
|
|
*/
|
|
}
|