Decimal has no rounded()

Hi, i was wondering why the Decimal swift type didn't have a rounded() function. Relying on casting to NSDecimalNumber for that feels really ugly. Is there something i've missed ?

2 Likes

You don't have to cast, you can use func NSDecimalRound(_:_:_:_:). But yes, for some arithmetics you will have to resort to an NSDecimal... function. I do not know the design reasons though.

This was oversight when introducing the struct type, unfortunately. We've got a Radar tracking this internally (rdar://problem/35857141 for those interested), but if you have a moment do you mind please filing a JIRA?

Falling back to NSDecimalNumber shouldn't be necessary, and using NSDecimalRound is quite painful:

var decimal = Decimal(1000.1234)
var rounded = Decimal()
NSDecimalRound(&rounded, &decimal, 2, .bankers) // requires rounded to be initialized, and decimal to be a var

I think ideally we'd add

func rounded(_ scale: Int = 0, _ roundingMode: RoundingMode = .plain) -> Decimal
mutating func round(_ scale: Int = 0, _ roundingMode: RoundingMode = .plain)
8 Likes

Perhaps the more general question is: Why doesn't Decimal conform to FloatingPoint?

1 Like

FloatingPoint requires IEEE 754 semantics, and Decimal does not provide them.

4 Likes

The FloatingPoint documentation doesn't mention anything about IEEE 754 upfront. I had to read quite a bit until I found what I guess is the relevant section:

Types that conform to the FloatingPoint protocol provide most basic (clause 5) operations of the IEEE 754 specification. The base, precision, and exponent range are not fixed in any way by this protocol, but it enforces the basic requirements of any IEEE 754 floating-point type.

What are some concrete examples of how Decimal doesn't comply with the above?

Asking because I saw this: https://gist.github.com/natecook1000/27d31d73315ffc2c80a7b4efc5788bd0
I haven't tested it to see if it works, because I assume it really can't?

Decimal could conform to FloatingPoint in the "provides all of the API surface" but not in the "... with the protocol-defined semantics" sense.

There's a whole bunch of ways in which it breaks down, but two name just two concrete ones that come immediately to mind: there's no infinity and no fused multiply-add operation.

7 Likes

jira filled : https://bugs.swift.org/browse/SR-8173

2 Likes

Thanks for filing! Related it to the Radar.

We really need two new protocols that refine Numeric & Comparable, one for integers that don't necessarily match BinaryInteger and one for fractional-supporting types that don't necessarily match FloatingPoint. (Would we need two SignedNumeric-refined variants too?)

BinaryInteger and FloatingPoint would probably refine these new protocols.

What would the semantics of these protocols be, and what stdlib types would conform to them?

If anyone else is interested in solving this in the short term, this is the extension that I used in a project I'm currently working on:

extension Decimal {
    mutating func round(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) {
        var localCopy = self
        NSDecimalRound(&self, &localCopy, scale, roundingMode)
    }

    func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal {
        var result = Decimal()
        var localCopy = self
        NSDecimalRound(&result, &localCopy, scale, roundingMode)
        return result
    }
}
1 Like