Nicola Prada Nicola Prada - 6 months ago 42
Swift Question

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?


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)")