nico9T
nico9T

Reputation: 2726

Swift Remainder operator precision

I need to round stocks, indices and futures prices to the nearest tick. The first step is to look if the price is a multiple of the tick. Apple docs says "Unlike the remainder operator in C and Objective-C, Swift’s remainder operator can also operate on floating-point numbers".

If I write the following code in a playground or in a console app and I run it, I expect 0 as result but I get a remainder value equals to 0.00999999999999775:

var stringPrice = "17.66"
var price = Double(stringPrice)
var tickSize: Double = 0.01

let remainder = price! % ticksize

This problem breaks my rounding function when using values such 17.66 as aPrice and 0.01 as aTickSize:

func roundPriceToNearestTick(Price aPrice: Double, TickSize a TickSize: Double)-> Double{
    let remainder = aPrice % aTickSize
    let shouldRoundUp = remainder >= aTickSize/2 ? true : false
    let multiple = floor(aPrice/aTickSize)
    let returnPrice = !shouldRoundUp ? aTickSize*multiple : aTickSize*multiple + aTickSize
    return returnPrice
}

What is the best way to fix this?

Upvotes: 1

Views: 829

Answers (1)

nico9T
nico9T

Reputation: 2726

Following the comments about the broken floating point math and the need to avoid floats and doubles for all the operations concerning money I changed my code to perform the remainder operation using NSDecimalNumbers. This seems to solve the precision problem.

var stringPrice = "17.66"
var tickSizeDouble : Double = 0.01
var tickSizeDecimalNumber: NSDecimalNumber = 0.01

func decimalNumberRemainder(Dividend aDividend: NSDecimalNumber, Divisor aDivisor: NSDecimalNumber)->NSDecimalNumber{

    let behaviour = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundDown,
                                                  scale: 0,
                                       raiseOnExactness: false ,
                                        raiseOnOverflow: false,
                                       raiseOnUnderflow: false,
                                    raiseOnDivideByZero: false )

    let quotient = aDividend.decimalNumberByDividingBy(aDivisor, withBehavior: behaviour)
    let subtractAmount = quotient.decimalNumberByMultiplyingBy(aDivisor)
    let remainder = aDividend.decimalNumberBySubtracting(subtractAmount)
    return remainder
}


let doubleRemainder = Double(stringPrice)! % tickSizeDouble
let decimalRemainder = decimalNumberRemainder(Dividend: NSDecimalNumber(string: stringPrice), Divisor:tickSizeDecimalNumber)


print("Using Double: \(doubleRemainder)")
print("Using NSDecimalNumber: \(decimalRemainder)")

Upvotes: 1

Related Questions