[swift-evolution] Pre-proposal: Safer Decimal Calculations
Haravikk
swift-evolution at haravikk.me
Sat Mar 19 06:06:29 CDT 2016
Just wanted to expand on the type tolerances idea with an example:
let a:Float±0.1 = 1234.56
let b:Float±0.5 = 123.456
let result:Float±0.25 = a + b // Error as b’s tolerance > 0.25
Of course this still requires developers to actually specify the tolerances at some point, however Swift’s type inference could allow them to be passed down. For example, if I omitted the type for result, then Swift could infer it as Float±0.5 as that is the higher tolerance of the two values. A plain definition of Float would be equivalent Float±infinity.
This would also make the proposed tilde operator even more useful as it could be used to ignore exceptions to the tolerance, so I could change my last line to:
let result:Float±0.25 = a + ~b
This allows me to use the value of b (albeit with potential error higher than I’d like), without changing the tolerance of result for later operations.
Also, once we have a full accuracy decimal type, I wonder if perhaps we should make it the default on the grounds of safety? While it might be overkill for many programs, I think that those programs that would see a performance impact would be too burdened by having to specify “unsafe” floating point by setting the Double or Float type (with or without a tolerance).
> On 19 Mar 2016, at 00:15, Haravikk via swift-evolution <swift-evolution at swift.org> wrote:
>
> I agree with all of this; I don’t really know enough to comment on the specific implementation of a decimal type, but we definitely need something other than NSDecimal.
>
> I don’t know if it’s possible, but I think that tolerances should be able to take a percentage. For example, I could write 0.0000001±1%, which would be much clearer (and less error prone) than 0.0000001± 0.000000001
>
> This is also useful because I think we could also benefit from the addition of tolerances to types, allowing us to declare something like: var foo:Float±0.01 = 0, which specifies a floating point value that the Swift compiler will not allow values to be added/substracted etc. to/from if they have a tolerance higher than my requirement. While cumulative error could still result in issues, if my tolerance is set reasonably low for my use-case then it would allow me to limit how much error I can accumulate in the lifetime of my data. In this being able to specify a percentage is useful when the variable has a clear right hand side such as var foo:Float±5% = 0.1 (effectively var foo:Float±0.005 = 0.1).
>
>> On 18 Mar 2016, at 22:42, Rainer Brockerhoff via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> First draft towards a tentative pre-proposal:
>> https://gist.github.com/rbrockerhoff/6874a5698bb479886e83 <https://gist.github.com/rbrockerhoff/6874a5698bb479886e83>
>> ------
>>
>> Pre-proposal: Safer Decimal Calculations
>> Proposal: TBD
>> Author(s): Rainer Brockerhoff
>> Status: TBD
>> Review manager: TBD
>>
>> Quoting the “The Swift Programming Language” book: “Swift adopts safe
>> programming patterns…”; “Swift is friendly to new programmers”. The
>> words “safe” and “safety” are found many times in the book and in online
>> documentation. The usual rationale for safe features is, to quote a
>> typical sentence, “…enables you to catch and fix errors as early as
>> possible in the development process”.
>>
>> One frequent stumbling point for both new and experienced programmers
>> stems from the vagaries of binary floating-point arithmetic. This
>> tentative pre-proposal suggests one possible way to make the dangers
>> somewhat more clear.
>>
>> My intention here is to start a discussion on this to inform the ongoing
>> (and future) reasoning on extending and regularising arithmetic in Swift.
>>
>> Motivation
>>
>> Floating-point hardware on most platforms that run Swift — that is,
>> Intel and ARM CPUs — uses the binary representation forms of the IEEE
>> 754-2008 standard. Although some few mainframes and software libraries
>> implement the decimal representations this is not currently leveraged by
>> Swift. Apple's NSDecimal and NSDecimalNumber implementation is awkward
>> to use in Swift, especially as standard arithmetic operators cannot be
>> used directly.
>>
>> Although it is possible to express floating-point constants in
>> hexadecimal (0x123.AB) with an optional binary exponent (0x123A.Bp-4),
>> decimal-form floating-point constants (123.45 or 1.2345e2) are extremely
>> common in practice.
>>
>> Unfortunately it is tempting to use floating-point arithmetic for
>> financial calculations or other purposes such as labelling graphical or
>> statistical data. Constants such as 0.1, 0.01, 0.001 and variations or
>> multiples thereof will certainly be used in such applications — and
>> almost none of these constant can be precisely represented in binary
>> floating-point format.
>>
>> Rounding errors will therefore be introduced at the outset, causing
>> unexpected or outright buggy behaviour down the line which will be
>> surprising to the user and/or the programmer. This will often happen at
>> some point when the results of a calculation are compared to a constant
>> or to another result.
>>
>> Current Solution
>>
>> As things stand, Swift's default print() function, Xcode playgrounds
>> etc. do some discreet rounding or truncation to make the problem less
>> apparent - a Double initialized with the literal 0.1 prints out as 0.1
>> instead of the exact value of the internal representation, something
>> like 0.100000000000000005551115123125782702118158340454101562.
>>
>> This, unfortunately, masks this underlying problem in settings such as
>> “toy” programs or educational playgrounds, leading programmers to be
>> surprised later when things won't work. A cursory search on
>> StackOverflow reveals tens of thousands of questions with headings like
>> “Is floating point math broken?".
>>
>> Warning on imprecise literals
>>
>> To make decimal-format floating-point literals safe, I suggest that the
>> compiler should emit a warning whenever a literal is used that cannot be
>> safely represented as an exact value of the type expected. (Note that
>> 0.1 cannot be represented exactly as any binary floating-point type.)
>>
>> The experienced programmer will, however, be willing to accept some
>> imprecision under circumstances that cannot be reliably determined by
>> the compiler. I suggest, therefore, that this acceptance be indicated by
>> an annotation to the literal; a form such as ~0.1 might be easiest to
>> read and implement, as the prefix ~ operator currently has no meaning
>> for a floating-point value. A “fixit” would be easily implemented to
>> insert the missing notation.
>>
>> Conversely, to avoid inexperienced or hurried programmers to strew ~s
>> everywhere, it would be useful to warn, and offer to fix, if the ~ is
>> present but the literal does have an exact representation.
>>
>> Tolerances
>>
>> A parallel idea is that of tolerances, introducing an ‘epsilon’ value to
>> be used in comparisons. Unfortunately an effective value of the epsilon
>> depends on the magnitude of the operands and there are many edge cases.
>>
>> Introducing a special type along the lines of “floating point with
>> tolerances” — using some accepted engineering notation for literals like
>> 100.5±0.1 — might be useful for specialised applications but will not
>> solve this specific problem. Expanding existing constructs to accept an
>> optional tolerance value, as has been proposed elsewhere, may be useful
>> in those specific instances but not contribute to raise programmer
>> awareness of unsafe literals.
>>
>> Full Decimal type proposal
>>
>> There are cogent arguments that prior art/habits and the already complex
>> interactions between Double, Float, Float80 and CGFloat are best left alone.
>>
>> However, there remains a need for a precise implementation of a workable
>> Decimal value type for financial calculations. IMHO repurposing the
>> existing NSDecimalNumber from Objective-C is not the best solution.
>>
>> As most experienced developers know, the standard solution for financial
>> calculations is to internally store fixed-point values — usually but not
>> always in cents — and then print the “virtual” point (or decimal comma,
>> for the rest of us) on output.
>>
>> I propose, therefore, an internal data layout like this:
>>
>> UInt16 - position of the “virtual” point, starting at 0
>> UInt16 - data array size - 1
>> [Int32] - contiguous data array, little-endian order, grown as needed.
>> Note that both UInt16 fields being zero implies that the number is
>> reduced to a 32-bit Integer. Number literals in Swift can be up to 2048
>> bits in size, so the maximum data array size would be 64, although it
>> could conceivably grow beyond that. The usual cases of the virtual point
>> position being 0 or 2 could be aggressively optimized for normal
>> arithmetic operators.
>>
>> Needless to say such a Decimal number would accept and represent
>> literals such as 0.01 with no problems. It would also serve as a BigNum
>> implementation for most purposes.
>>
>> No doubt implementing this type in the standard library would allow for
>> highly optimized implementations for all major CPU platforms. In
>> particular, the data array should probably be [Int64] for 64-bit platforms.
>>
>> Acknowledgement
>>
>> Thanks to Erica Sadun for their help with an early version of this
>> pre-proposal.
>>
>> Some references
>>
>> http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
>> https://docs.python.org/2/tutorial/floatingpoint.html
>> https://en.wikipedia.org/wiki/IEEE_floating_point
>> https://randomascii.wordpress.com/category/floating-point/
>> http://code.jsoftware.com/wiki/Essays/Tolerant_Comparison
>>
>> --
>> Rainer Brockerhoff <rainer at brockerhoff.net>
>> Belo Horizonte, Brazil
>> "In the affairs of others even fools are wise
>> In their own business even sages err."
>> http://brockerhoff.net/blog/
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160319/539467ff/attachment.html>
More information about the swift-evolution
mailing list