-1

SWIFT/IOS Implementing some arithmetic on share prices, doing a subtraction to arrive at a daily loss/gain figure

PLEASE NOTE: These are not accounting calculations, they are all about measuring investment performance therefore absolute accuracy is not required, instead the priority is completing trillions of calculations in a short a time as possible (sub second) - hence the use of floating point arithmetic. The problem I have described below becomes important because a very small multiplication error can become significant at the scale I am performing the calculations.

When using the default Float type, I get inaccurate results (see playground code) below.

import UIKit

var str = "Hello, playground"

let y: Float = 129.08

let t: Float = 130.29

let subtracted = t - y // results in 1.209991 !!!!!! not accurate, should be something like 1.209998

let returnVal = subtracted / y // Results in 0.009373966 also inaccurate

If I implement the same arithmetic in a spreadsheet I get the expected results (or near as dammit): 130.29 - 129.08 = 1.20999999999998.

Am I making some fundamental error? And one have experience of this? How can I get the same results as the spreadsheet (against which I am validating my calculations).

OK so I updated to include a conversions from Float to Double as suggested in the replies and look at the result! The Double(float_value) conversion seems to add garbage in the lower significance digits of the result.

import UIKit

var str = "Hello, playground"

let y_float: Float = 129.08

let t_float : Float = 130.29

let y: Double = Double(y_float) // results in 129.0800018310547 !!!

let t: Double = Double(t_float) // results in 130.2899932861328

let subtracted : Double = t - y

let returnVal = subtracted / y

So now the question is, any way to make a clean conversion from Float to Double?

Tomm P
  • 761
  • 1
  • 8
  • 19
  • 1
    Perhaps it's a [floating point rounding error](https://floating-point-gui.de/errors/rounding/)? – trojanfoe Feb 11 '19 at 08:39
  • `Float` has a precision of ~6 decimal digits ... – Martin R Feb 11 '19 at 08:41
  • I'd accept the rounding error if the error was the same as for the spreadsheet - i.e. 1.209998 or something similar. – Tomm P Feb 11 '19 at 08:41
  • The spread sheet probably uses a representation with a higher precision (such as `Double` ) – Martin R Feb 11 '19 at 08:43
  • Martin R - the rounding error in the spreadsheet is different (i.e. the spreadsheet is more accurate). I'm interested in the exact mechanisms at work and why there's a difference between spreadsheet and code. – Tomm P Feb 11 '19 at 08:44
  • One more comment - sorry for being verbose. I understand there might be a rounding error but the consequence is that if I want to compare with a spreadsheet to check results, I have to limit the result in the code and in the spreadsheet to 4 decimal places as the source numbers are 2 decimal places. That's kinda inconvenient and adds additional code. – Tomm P Feb 11 '19 at 08:47
  • 1
    With double-precision floating point (`Double`) you'll get 1.2099999999999795, and rounding that to 14 decimal digits gives 1.20999999999998. That *might* be what your spreadsheet does. – Martin R Feb 11 '19 at 08:59
  • 1
    Your rounding issue is irrelevant, if you subtract two numbers with a 2 digit precision each then the answer only has a two digit precision and nothing more. Similar for division – Joakim Danielson Feb 11 '19 at 08:59
  • 2
    Related: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – with links to additional documentation, such as [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – Martin R Feb 11 '19 at 09:03
  • Jockim Danielson - agree that this is the case, I'm just a bit peeved the complier cannot take care of trimming off the garbage. I'll have to add the code similar to suggested by AtulParmar below to correct the calculations in both the App and the spreadsheet. I suppose this is part of why Swift is fast. – Tomm P Feb 11 '19 at 09:06
  • 1
    @TommP: Float and Double are binary floating point numbers, and neither of them can store the number 129.08 exactly – try `let y_float: Float = 129.08 ; print(String(format: "%.14f", y_float))`. It only *looks* as if the conversion to Double adds garbage, because a Double is (by default) printed with more fractional digits. – Martin R Feb 11 '19 at 09:13
  • @Martin R: Thanks for the replies. I guess I have to bite the bullet and do some trimming in both code and spreadsheet to get consistency. – Tomm P Feb 11 '19 at 09:15
  • 3
    Always use `Decimal` to represent financial values, such as share prices. – Dávid Pásztor Feb 11 '19 at 09:50
  • The conversion from `Float` to `Double` does not change the values at all. The statement `let y_float: Float = 129.08` sets `y_float` to exactly 129.0800018310546875. Some default formatting for this may show the value as “129.08000” or a similar display because default formatting uses only a few digits for `Float`. When converted to `Double`, the value is exactly the same. But default formatting for `Double` uses more digits, so it may show something like “129.08000183105469”. – Eric Postpischil Feb 11 '19 at 11:59
  • @Dávid Pásztor: To clarify, this question does not relate to the representation of share prices, it relates to the calculation of percentage returns and variances which by their very nature are not finite amounts. – Tomm P Nov 27 '19 at 08:37

3 Answers3

1

Use Double

let y: Double = 129.08

let t: Double = 130.29

let subtracted: Double = t - y
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Alex Bailey
  • 793
  • 9
  • 20
  • OK so I had tried with double earlier and found the results also inconsistent with the spreadsheet. Is I just tried again and found that converting a Float to a double seems to add garbage in the low significance digits !! – Tomm P Feb 11 '19 at 08:55
1

Float and Double use a base-2 internal encoding so there will always be some base-10 values that they cannot accurately represent.

If you want to use true decimal (base-10) calculations, use the Decimal type:

let y = Decimal(string:"129.08")!

let t = Decimal(string:"130.29")!

let subtracted = t - y // results in 1.21

let returnVal = subtracted / y // Results in 0.00937403160830492717694453052370622869

Note that I used strings to initialize the values because literals would be first processed as Float and would produce the same inaccuracies before the assignments.

Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • The performance of calculations using Decimal rather than Float/Double will be interesting. I intend to do literally billions of multiplications on these values in real time using Metal. I also wander if the representation of a Decimal is compatible with metal - I am guessing not. – Tomm P Feb 12 '19 at 05:08
-1

OK so I looked around and found

Rounding a double value to x number of decimal places in swift

Which I will use to tidy up the floating point values and remove the inaccuracy. I'll also implement similar truncation in my spreadsheet to ensure alignment.

Here is the code:

import UIKit

var str = "Hello, playground"

var y_float: Float = 129.08

let t_float : Float = 130.29

var y: Double = Double(y_float * 1000).rounded() / 1000 // results in 129.08

var t: Double = Double(t_float * 1000).rounded() / 1000 // results in 130.29

let subtracted : Double = t - y

let returnVal = subtracted / y
Tomm P
  • 761
  • 1
  • 8
  • 19
  • This is generally a bad idea. “Rounding” numbers to make them **look good** in decimal does not improve their mathematical quality. It introduces additional error. If you need to calculate any sort of whole units (numbers of people, cupcakes, cents, thousandths of a stock share, whatever), then integer arithmetic is often a better choice. If you are going to use floating-point arithmetic, then it is important to truly **understand** floating-point arithmetic (learn its representation and the rules of its arithmetic) rather than apply kludgy patches. – Eric Postpischil Feb 11 '19 at 13:32
  • @Eric Postpischil - appreciate the point you make, thanks. I'm revising where I'm using Float/Double or Decimal values throughout. Learned some interesting things regards Float/Double today - still not convinced it's working as it should. As it happens the usage I'm making of Float/Double in this case is valid because I'm computing % returns and variance of probabilities rather than tracking absolute prices or numbers of securities. – Tomm P Feb 11 '19 at 18:35