[swift-evolution] Pre-proposal: Safer Decimal Calculations
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.
>> 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.
>> 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.
>> Thanks to Erica Sadun for their help with an early version of this
>> Some references
>> 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."
>> swift-evolution mailing list
>> swift-evolution at swift.org
> swift-evolution mailing list
> swift-evolution at swift.org
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution