PDA

View Full Version : Simple Division Function with no loss...

Peter Bragg
14-Jun-2007, 11:11 AM
A couple of little functions that someone might find useful.

Sometimes you need to do a division calculation but end up losing something!
For example, divide 10 by 3 to 2 decimal places. You get an answer of 3.33.
Fine, I am not going to quibble with that, except that if you then do 3.33 x
3, you don't end up with 10, you end up with 9.99. You've lost 0.01!

This might not seem important but if you are dealing with larger amounts and
those amounts are for example financial figures your accountant may want to
know where the missing money is!

So here is a little division function that simply splits the passed value
into the specified number of 'equal' chunks but such that nothing goes
missing (- so not necessarily equal then). It returns an array of values
that will always add up to give you the number you started with.

The return values will be to the same degree of accuracy as the passed value
that we are dividing, with the exception that if the passed value is an
integer it will default to 2 decimal places.

So for example, 10 divided by 3 will return the 3 values 3.33, 3.33 and 3.34

where as 10.1 divided by 3 will return 3.4, 3.3 and 3.3. Here the return
values are only to 1 decimal place because that is equal to the number of
places in the number 10.1. You may decide that you always want to return a
fixed number of decimal places though - please feel free to change the code!

The function calls a rounding function so here are the 2. You may have (and
would prefer to use) your own rounding function though which is why it is
separate.

//RoundToN is simply a rounding function that rounds a number to the given
number of decimal places.

Function RoundToN Global Number nRounding Integer iDecPlaces Returns Number
Move (10^iDecPlaces) to iDecPlaces
Move (nRounding * iDecPlaces) to nRounding
Move (Round(nRounding)) to nRounding
Move (nRounding / iDecPlaces) to nRounding
Function_Return nRounding
End_Function

//DivideWithNoLoss returns an array of values that always add up to the
number you started with - so you don't lose anything.

Function DivideWithNoLoss Global Number nNumToDivide Integer iDivideBy
Returns Number[]
Number[] nResults
Number nResult
Integer iDecPlaces iPos
String sNumToDivide

Move (String(nNumToDivide)) to sNumToDivide
Move (Pos(".",sNumToDivide)) to iPos
If (iPos<>0) Begin
Move (length(sNumToDivide)-iPos) to iDecPlaces
End
Else Move 2 to iDecPlaces

Repeat
Move (nNumToDivide/iDivideBy) to nResult
Get RoundToN nResult iDecPlaces to nResult
Move nResult to nResults[SizeOfArray(nResults)]
Move (nNumToDivide - nResult) to nNumToDivide
Decrement iDivideBy
Until (iDivideBy=0)
Move (SortArray(nResults)) to nResults

Function_Return nResults
End_Function

So

number[] nResults
Get DivideWithNoLoss 11.002 3 to nResults

will give you: 3.667, 3.667 and 3.668. Add 'em all up again and you're back
to 11.002

I have had this function for a while and forgot all about it. It is not
rocket science but someone somewhere (other than us) might find it useful -
you never know...

Peter Bragg

Dalton Pulsipher
11-Jul-2007, 01:10 PM
Since irrational numbers are not a problem here (given that the scope is to
take a definite number of decimal places and deal with them) it would seem
that you could always store the remaining part of the number as two
additional integers. They would just be the top and bottom of the fraction
that equals the part cut off by rounding. This may keep you from having to
keep up with and store an array of additional addup values for each number.

For example, if 10 / 3 = 3.33 then you have left off 0.00333333...

But 0.00333333... = 1 / 300, so you would just store 3.33, 1, 300

The more you deal with this though, the bigger the fractions get. And you
may end up with operands greater than the largest number-based datatype can
hold. In this case you would have to go to string multiplication or a
BigInt library through an API to handle arbitrarily large numbers.

It all comes down to the amount of precision needed at any one time.
Although a remainder is useful at one point it would be ridiculous for a
company to keep up with the fractions of pennies you owe them from each time
you purchase a candy bar until you finally have to pay another penny for it.
When the transaction is completed the remainder is to be dropped.

All this just to note that either solution can be used for calculation of
precise numbers.

BTW, if anyone does need string math functions for arbitrarily large numbers
in either base 10 or base 2 format I have already written them. The
multiplication function is considerably more efficient than creating arrays
of multiplied numbers and adding them up.

Dalton

"Peter Bragg" <tech@care-data.co.uk> wrote in message
news:X5mSw6prHHA.3152@dacmail.dataaccess.com...
>A couple of little functions that someone might find useful.
>
> Sometimes you need to do a division calculation but end up losing
> something! For example, divide 10 by 3 to 2 decimal places. You get an
> answer of 3.33. Fine, I am not going to quibble with that, except that if
> you then do 3.33 x 3, you don't end up with 10, you end up with 9.99.
> You've lost 0.01!
>
> This might not seem important but if you are dealing with larger amounts
> and those amounts are for example financial figures your accountant may
> want to know where the missing money is!
>
> So here is a little division function that simply splits the passed value
> into the specified number of 'equal' chunks but such that nothing goes
> missing (- so not necessarily equal then). It returns an array of values
> that will always add up to give you the number you started with.
>
> The return values will be to the same degree of accuracy as the passed
> value that we are dividing, with the exception that if the passed value is
> an integer it will default to 2 decimal places.
>
> So for example, 10 divided by 3 will return the 3 values 3.33, 3.33 and
> 3.34
>
> where as 10.1 divided by 3 will return 3.4, 3.3 and 3.3. Here the return
> values are only to 1 decimal place because that is equal to the number of
> places in the number 10.1. You may decide that you always want to return a
> fixed number of decimal places though - please feel free to change the
> code!
>
> The function calls a rounding function so here are the 2. You may have
> (and would prefer to use) your own rounding function though which is why
> it is separate.
>
>
> //RoundToN is simply a rounding function that rounds a number to the given
> number of decimal places.
>
> Function RoundToN Global Number nRounding Integer iDecPlaces Returns
> Number
> Move (10^iDecPlaces) to iDecPlaces
> Move (nRounding * iDecPlaces) to nRounding
> Move (Round(nRounding)) to nRounding
> Move (nRounding / iDecPlaces) to nRounding
> Function_Return nRounding
> End_Function
>
> //DivideWithNoLoss returns an array of values that always add up to the
> number you started with - so you don't lose anything.
>
> Function DivideWithNoLoss Global Number nNumToDivide Integer iDivideBy
> Returns Number[]
> Number[] nResults
> Number nResult
> Integer iDecPlaces iPos
> String sNumToDivide
>
> Move (String(nNumToDivide)) to sNumToDivide
> Move (Pos(".",sNumToDivide)) to iPos
> If (iPos<>0) Begin
> Move (length(sNumToDivide)-iPos) to iDecPlaces
> End
> Else Move 2 to iDecPlaces
>
> Repeat
> Move (nNumToDivide/iDivideBy) to nResult
> Get RoundToN nResult iDecPlaces to nResult
> Move nResult to nResults[SizeOfArray(nResults)]
> Move (nNumToDivide - nResult) to nNumToDivide
> Decrement iDivideBy
> Until (iDivideBy=0)
> Move (SortArray(nResults)) to nResults
>
> Function_Return nResults
> End_Function
>
>
> So
>
> number[] nResults
> Get DivideWithNoLoss 11.002 3 to nResults
>
> will give you: 3.667, 3.667 and 3.668. Add 'em all up again and you're
> back to 11.002
>
>
> I have had this function for a while and forgot all about it. It is not
> rocket science but someone somewhere (other than us) might find it
> useful - you never know...
>
>
> Peter Bragg
>
>
>
>

Dave Robinson
4-Aug-2007, 08:09 AM
Peter,

belated thanks for this. One of our ^%\$@* vendors has just come up with a line that he wants priced at 3 for \$20. Wouldn't be a problem execpt that ther are several SKUs and he wants to mix and match.....

>>> Peter Bragg<tech@care-data.co.uk> 6/14/2007 11:11:30 AM >>>
A couple of little functions that someone might find useful.

Sometimes you need to do a division calculation but end up losing something!
For example, divide 10 by 3 to 2 decimal places. You get an answer of 3.33.
Fine, I am not going to quibble with that, except that if you then do 3.33 x
3, you don't end up with 10, you end up with 9.99. You've lost 0.01!

This might not seem important but if you are dealing with larger amounts and
those amounts are for example financial figures your accountant may want to
know where the missing money is!

So here is a little division function that simply splits the passed value
into the specified number of 'equal' chunks but such that nothing goes
missing (- so not necessarily equal then). It returns an array of values
that will always add up to give you the number you started with.

The return values will be to the same degree of accuracy as the passed value
that we are dividing, with the exception that if the passed value is an
integer it will default to 2 decimal places.

So for example, 10 divided by 3 will return the 3 values 3.33, 3.33 and 3.34

where as 10.1 divided by 3 will return 3.4, 3.3 and 3.3. Here the return
values are only to 1 decimal place because that is equal to the number of
places in the number 10.1. You may decide that you always want to return a
fixed number of decimal places though - please feel free to change the code!

The function calls a rounding function so here are the 2. You may have (and
would prefer to use) your own rounding function though which is why it is
separate.

//RoundToN is simply a rounding function that rounds a number to the given
number of decimal places.

Function RoundToN Global Number nRounding Integer iDecPlaces Returns Number
Move (10^iDecPlaces) to iDecPlaces
Move (nRounding * iDecPlaces) to nRounding
Move (Round(nRounding)) to nRounding
Move (nRounding / iDecPlaces) to nRounding
Function_Return nRounding
End_Function

//DivideWithNoLoss returns an array of values that always add up to the
number you started with - so you don't lose anything.

Function DivideWithNoLoss Global Number nNumToDivide Integer iDivideBy
Returns Number[]
Number[] nResults
Number nResult
Integer iDecPlaces iPos
String sNumToDivide

Move (String(nNumToDivide)) to sNumToDivide
Move (Pos(".",sNumToDivide)) to iPos
If (iPos<>0) Begin
Move (length(sNumToDivide)-iPos) to iDecPlaces
End
Else Move 2 to iDecPlaces

Repeat
Move (nNumToDivide/iDivideBy) to nResult
Get RoundToN nResult iDecPlaces to nResult
Move nResult to nResults[SizeOfArray(nResults)]
Move (nNumToDivide - nResult) to nNumToDivide
Decrement iDivideBy
Until (iDivideBy=0)
Move (SortArray(nResults)) to nResults

Function_Return nResults
End_Function

So

number[] nResults
Get DivideWithNoLoss 11.002 3 to nResults

will give you: 3.667, 3.667 and 3.668. Add 'em all up again and you're back
to 11.002

I have had this function for a while and forgot all about it. It is not
rocket science but someone somewhere (other than us) might find it useful -
you never know...

Peter Bragg

Raveen Ryan Sundram
15-Aug-2007, 07:43 PM
Hi Peter,

I had the same issue last year - what I found was to use a large decimal
points.

For example, I assume all number had 2 decimal points, and my base return
divided value would be (4+n) decimal points.
i.e
10/3 = 3.3334
10.1/3 = 3.3667

if decimals point gt 2, then n=(decimals-2), there (4+1)
10.247 / 7 = 1.46386 (5 decimals)
10.2478 / 7 = 1.463971 (6 decimals)

On multiplying, to round the results back to its base decimal points.

Rounding (3.3334*3,0) = 10
Rounding (3.3667*3,1) = 10.1
Rounding (1.46386*7,3) = 10.247
Rounding (1.463971*7,4) = 10.2478

Function Rounding Global Number Value Integer Point Returns Number
Local integer negative
Local Integer b4_round
Local Number round_value d_nator

Move (value<0) To negative
If (negative) Calc (value*-1) To value

Move (10^point) To d_nator
Move (value*d_nator+0.5) To b4_round
Calc (Number(b4_round)/d_nator) To round_value

If (negative) Calc (round_value*-1) To round_value

Function_Return round_value
End_Function