MikeMcl / decimal.js

An arbitrary-precision Decimal type for JavaScript
http://mikemcl.github.io/decimal.js
MIT License
6.35k stars 480 forks source link

Decimal.sum to support no arguments #207

Open yassh opened 2 years ago

yassh commented 2 years ago

Currently, an error occurs when zero arguments is passed to Decimal.sum.

const decimals = []
Decimal.sum(...decimals) // Error occurs

Lodash's _.sum and Ramda's R.sum returns 0 when passed an array with zero elements.

const numbers = []
_.sum(numbers) // => 0
R.sum(numbers) // => 0

Similarly, it would be nice if Decimal.sum returned new Decimal(0) when called with zero arguments.

const decimals = []
Decimal.sum(...decimals) // => new Decimal(0)
MikeMcl commented 2 years ago

I think this has been requested before.

Should Decimal.max and min then behave similarly?

And the prototype methods?

new Decimal(1).add()    // => new Decimal(1)

Personally, I think it is better (safer) to throw when undefined is passed.

yassh commented 2 years ago

Should Decimal.max and min then behave similarly?

Math.max returns -Infinity and Math.min returns Infinity when no arguments are passed.

Math.max() // => -Infinity
Math.min() // => Infinity

So, I propose that Decimal.max and Decimal.min behave similarly.

Decimal.max() // => new Decimal(-Infinity)
Decimal.min() // => new Decimal(Infinity)
gterras commented 1 year ago

Just my user voice but I highly appreciate the simplicity of the lib as it is. Not having to remember these kind of subtleties is a plus.

michaelhays commented 1 year ago

For my use case, I need to retrieve the sum of a highly nested and filtered array of objects, and it would be very convenient if Decimal.sum() == 0.

I'd love to be able to do this:

const value = Decimal.sum(...myArray.filter(filterFunc).map(mapFunc))

But because myArray.filter(filterFunc).map(mapFunc) might be an empty array, I'm currently having to do this:

const value =
  myArray.filter(filterFunc).length > 0
    ? Decimal.sum(...myArray.filter(filterFunc).map(mapFunc))
    : new Decimal(0)

(In reality, I have many more filters and maps than this example, but it paints the picture)

Though honestly, I think it would be ideal if Decimal.sum accepted a single array as an argument (like lodash.sumBy), where Decimal.sum([]) == 0.

gterras commented 1 year ago

The concern is legit since some popular libs do this, it could make a useful option.

Note that you can still one line your sum with

myArr.reduce((a: Decimal, b: Decimal) => a.plus(b), new Decimal(0)) 
// or even without acc
myArr.reduce((a: Decimal, b: Decimal) => a.plus(b)) 
hunterwilhelm commented 1 year ago

I just ran into this issue, it is not obvious that Decimal.sum(...[]) will throw an error. I am putting Decimal.sum(0, ...myArray) now to fix this issue and it's unappealing.

michaelhays commented 1 year ago

Yeah, still don't really love the API here.

I ended up just making a utility function which accepts an array (instead of multiple arguments), and returns 0 for an empty array or undefined argument:

import Decimal from 'decimal.js'

/**
 * Sum an array of Decimal values, returning 0 for undefined or empty arrays.
 */
export default function sumDecimal(decimals?: Decimal[]): Decimal {
  return !decimals || decimals.length === 0
    ? new Decimal(0)
    : Decimal.sum(...decimals)
}
smaspe commented 3 months ago

If an empty array as argument crashes, the function should not accept 0 arguments?