Better to be roughly right than precisely wrong: Rounding numbers with Swift

by Joey deVilla on January 26, 2016

ludicrous trek accuracy

Update (December 19, 2016): The code in this article was updated for Swift 3.0.

The 1960s Star Trek TV series wasn’t written by engineers, scientists, or mathematicians, which is why one of their tricks for showing you that Mr. Spock was an intellectual badass was having make calculations to a ridiculous degree of preciseness, a dramatic device that TV Tropes calls ludicrous precision.

You don’t want your apps to be as annoying as Spock could sometimes be, which is why rounding is an important operation.

Rounding is useful for things like:

  • The limits of your input precision. Suppose you’re calculating the average of the daily high temperatures over the past week, each of which is accurate to the nearest degree. Because of the way an average (or more technically, the mean) is calculated, the result may contain a decimal point. However, the result you report shouldn’t be more precise than the data it’s based on, so you’ll want to round it to the nearest degree.
  • The limits of usefulness. If you’re displaying directions for a short trip, you probably wouldn’t want to report the total distance with more than a 1/4 mile or 1/2 kilometer precision, and you’d probably want to state the trip time rounded to the nearest minute. On the other hand, for a trip covering the width of the United States or Canada, anything more precise than rounding the total distance to the nearest 10 miles and the trip time to the nearest half-hour isn’t generally helpful.
  • Practical considerations. When performing financial calculations, especially when dealing with cash, you’ll want to round your result to the nearest penny. In countries like Canada, where they’ve phased out the penny, you’ll want to round cash calculations to the nearest nickel.

Swift’s built-in rounding functions

Swift comes built-in with most of the rounding functions in C’s math library. The ones you’re most likely to use are round, floor, and ceil:

round(_:) Given a number n, this returns n rounded to the nearest whole number. For example:

  • round(2.0) returns 2.0
  • round(2.2) returns 2.0
  • round(2.5) returns 3.0
  • round(2.9) returns 3.0

With negative numbers, a fractional value of 0.5 or higher rounds the number down, and lower fractional values round the number up:

  • round(-2.0) returns –2.0
  • round(-2.2) returns –2.0
  • round(-2.5) returns –3.0
  • round(-2.9) returns –3.0
floor(_:) Given a number n, this returns n rounded down to the nearest whole number. For example:

  • floor(2.0) returns 2.0
  • floor(2.2) returns 2.0
  • floor(2.5) returns 2.0
  • floor(2.9) returns 2.0
  • floor(-2.0) returns -2.0
  • floor(-2.2) returns -3.0
  • floor(-2.5) returns -3.0
  • floor(-2.9) returns -3.0
ceil(_:) Given a number n, this returns n rounded up to the nearest whole number (you’ve probably guessed that ceil is short for ceiling). For example:

  • ceil(2.0) returns 2.0
  • ceil(2.2) returns 3.0
  • ceil(2.5) returns 3.0
  • ceil(2.9) returns 3.0
  • ceil(-2.0) returns -2.0
  • ceil(-2.2) returns -2.0
  • ceil(-2.5) returns -2.0
  • ceil(-2.9) returns -2.0

round, floor, and ceil each come in two versions:

  • A version that accepts a Double argument and returns a Double result (Doubles are 64-bit numbers and have a precision of at least 15 decimal digits), and
  • a version that accepts a Float argument and returns a Float result (Floats are 32-bit numbers and have a precision of as little as 6 decimal digits).

Rounding n to the nearest multiple of x

rounding coaster

round, floor, and ceil work well when you’re trying to round to nearest whole number, but they’re not as useful if you’re facing one of these rounding scenarios:

  • Rounding a number to the nearest 10 (as demonstrated in the “Rounding Coaster” shown above), 100, 1000, etc.
  • Rounding a number to the nearest .1, .01, .001, etc.
  • Rounding a dollar value to the nearest penny
  • In Canada, which has eliminated the penny (because it costs more than a penny to manufacture one), you calculate change by rounding to the nearest nickel
  • Rounding a number to the nearest multiple of x

For situations like the ones listed above, you’ll find these Swift functions handy:

Here’s the round(_:toNearest:) function in action:

Here’s the roundDown(_:toNearest:) function in action:

Here’s the roundUp(_:toNearest:) function in action:

Rounding to a specified number of decimal places

price break

Photo from Consumerist. Click to see the source.

If you want to round a value to a specific number of decimal places, try this function:

Here’s roundToPlaces(_:decimalPlaces:) in action:

Removing the fractional part of a number

seamus levine from family guy

“Seamus” from Family Guy is rather truncated.

Sometimes, you just want to lop off anything after the decimal point. That’s what trunc (short for truncate) is for:

Displaying rounded values

There are times when you want to store a precise value but display a rounded one. Use the String class’ init(format:arguments:) method and C’s format strings for printf to display numbers as strings representing rounded values. Here it is in action:

Happy rounding!

Thanks to John Maynard Keynes for the title of this article!

{ 7 comments… read them below or add one }

1 CHiPloveNY March 15, 2016 at 3:26 am

But:
roundUp(52.376, toNearest: 0.1) // 52.40000000000001

2 Dave October 7, 2016 at 4:15 pm

On a related point, how can you create a new type that stores dollars and cents as a floating point value with exactly 2 decimal places? If you do that, all this rounding becomes unnecessary.

I’m writing something to store financial values and it should not be possible to store fractions of a cent, so every value should be x.xx with no trailing figures after the .xx

I want this because for a bank account withdrawal, sometimes you need to check if balance is >= amount being withdrawn. Normal float/double values don’t usually work when checking equality.

3 Slipp Douglas Thompson March 25, 2017 at 1:56 pm

@CHiPloveNY: This is because not every number can be accurately stored in a float-point number.  Observe:

(52.4 as Double) // 52.399999999999999
(52.4 as Double).nestUp // 52.400000000000006

4 Slipp Douglas Thompson March 25, 2017 at 1:56 pm

@CHiPloveNY: This is because not every number can be accurately stored in a float-point number.  Observe:

(52.4 as Double) // 52.399999999999999
(52.4 as Double).nextUp // 52.400000000000006

5 Slipp Douglas Thompson March 25, 2017 at 2:08 pm

@Dave: Trying to use floating-point numbers for financials is the wrong tool for the job.  The reason is right in the name— the decimal point is “floating”.  What you need is a “fixed-point decimal number”.

Fortunately, Foundation provides a class for that— Decimal (or in Obj-C, NSDecimalNumber).  To create a Decimal number, you can use either of these:

let price1 = Decimal(sign: .plus, exponent: -2, significand: 199994) // 1999.94
let price2 = Decimal(string: “1999.94”)! // 1999.94

Compare to:

let price3 = Double(1999.94) // 1999.9400000000001

6 Slipp Douglas Thompson March 25, 2017 at 2:22 pm

@Dave: Also, the range of Decimal isn’t infinite, but it should certainly be big enough for any financial numbers you might need to do math on in the forseeable future.  The significand is stored as 128-bits, so given an exponent of -2 (for cents), that gives you a range of -$3.4 Undecillion to +$3.4 Undecillion (-$3,402,823,669,209,384,634,633,746,074,317,682,114.55 to 3,402,823,669,209,384,634,633,746,074,317,682,114.55).

Calculated using:

let min = Decimal(_exponent: -2, _length: 8, _isNegative: 1, _isCompact: 0, _reserved: 0, _mantissa: ( UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max ))
let max = Decimal(_exponent: -2, _length: 8, _isNegative: 0, _isCompact: 0, _reserved: 0, _mantissa: ( UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max ))

7 Kalyan July 10, 2017 at 2:24 pm

How to get 1.00 , if I provide 1.001 ?

Leave a Comment

{ 2 trackbacks }

Previous post:

Next post: