Proposal: Remove % operator for floating-point types

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

Rationale:
While C and C++ do not provide the “%” operator for floating-point types, many newer languages do (Java, C#, and Python, to name just a few). Superficially this seems reasonable, but there are severe gotchas when % is applied to floating-point data, and the results are often extremely surprising to unwary users. C and C++ omitted this operator for good reason. Even if you think you want this operator, it is probably doing the wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does not hold for floating-point types, because a/b does not produce an integral value. If it did produce an integral value, it would need to be a bignum type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the division algorithm axiom, % would still be deeply flawed. Whereas people are generally used to modest rounding errors in floating-point arithmetic, because % is not continuous small errors are frequently enormously magnified with catastrophic results:

  (swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point; the actual value of “0.1” is 0.1000000000000000055511151231257827021181583404541015625. Other than that rounding, the entire computation is exact.]

Proposed Approach:
Remove the “%” operator for floating-point types. The operation is still be available via the C standard library fmod( ) function (which should be mapped to a Swiftier name, but that’s a separate proposal).

Alternative Considered:
Instead of binding “%” to fmod( ), it could be bound to remainder( ), which implements the IEEE 754 remainder operation; this is just like fmod( ), except instead of returning the remainder under truncating division, it returns the remainder of round-to-nearest division, meaning that if a and b are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0, b). This still has a large discontinuity, but the discontinuity is moved away from zero, which makes it much less troublesome (that’s why IEEE 754 standardized this operation):

  (swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like normal floating-point rounding

The downside to this alternative is that now % behaves totally differently for integer and floating-point data, and of course the division algorithm still doesn’t hold.

Proposal:
Remove the % operator for floating-point types in Swift 3. Add a warning in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

Hi Steve,
I like this idea because it removes a potential "gotcha" / unexpected
behavior, and it will encourage people not to do stuff like this with
floating-point numbers. (Somewhat like the clang analyzer's
security.FloatLoopCounter
<http://clang-analyzer.llvm.org/available_checks.html#security_checkers&gt;
tries to do.) The remainder() behavior makes sense to me.

However, I think it'd be nice for the standard library to continue exposing
this behavior, if possible, rather than requiring the user to "import
Darwin" or "import Glibc". Can these remain as methods on the built-in
floating-point types, perhaps like .mod() and .remainder(), so they can be
used if truly desired?

Jacob

···

On Fri, Dec 18, 2015 at 1:12 PM, Stephen Canon via swift-evolution < swift-evolution@swift.org> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

*Rationale:*
While C and C++ do not provide the “%” operator for floating-point types,
many newer languages do (Java, C#, and Python, to name just a few).
Superficially this seems reasonable, but there are severe gotchas when % is
applied to floating-point data, and the results are often extremely
surprising to unwary users. C and C++ omitted this operator for good
reason. Even if you think you want this operator, it is probably doing the
wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If
b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does
not hold for floating-point types, because a/b does not produce an integral
value. If it did produce an integral value, it would need to be a bignum
type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has
over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the
division algorithm axiom, % would still be deeply flawed. Whereas people
are generally used to modest rounding errors in floating-point arithmetic,
because % is not continuous small errors are frequently enormously
magnified with catastrophic results:

(swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point;
the actual value of “0.1”
is 0.1000000000000000055511151231257827021181583404541015625. Other than
that rounding, the entire computation is exact.]

*Proposed Approach:*
Remove the “%” operator for floating-point types. The operation is still
be available via the C standard library fmod( ) function (which should be
mapped to a Swiftier name, but that’s a separate proposal).

*Alternative Considered:*
Instead of binding “%” to fmod( ), it could be bound to remainder( ),
which implements the IEEE 754 remainder operation; this is just like fmod(
), except instead of returning the remainder under truncating division, it
returns the remainder of round-to-nearest division, meaning that if a and b
are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0,
b). This still has a large discontinuity, but the discontinuity is moved
away from zero, which makes it much less troublesome (that’s why IEEE 754
standardized this operation):

(swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like
normal floating-point rounding

The downside to this alternative is that now % behaves totally differently
for integer and floating-point data, and of course the division algorithm
still doesn’t hold.

*Proposal:*
Remove the % operator for floating-point types in Swift 3. Add a warning
in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising operation like this to have such short and inviting syntax. As asked downthread, have you given any thought into whether a Decimal type would support this operation?

-Chris

···

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org> wrote:

Rationale:
While C and C++ do not provide the “%” operator for floating-point types, many newer languages do (Java, C#, and Python, to name just a few). Superficially this seems reasonable, but there are severe gotchas when % is applied to floating-point data, and the results are often extremely surprising to unwary users. C and C++ omitted this operator for good reason. Even if you think you want this operator, it is probably doing the wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does not hold for floating-point types, because a/b does not produce an integral value. If it did produce an integral value, it would need to be a bignum type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the division algorithm axiom, % would still be deeply flawed. Whereas people are generally used to modest rounding errors in floating-point arithmetic, because % is not continuous small errors are frequently enormously magnified with catastrophic results:

  (swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point; the actual value of “0.1” is 0.1000000000000000055511151231257827021181583404541015625. Other than that rounding, the entire computation is exact.]

Proposed Approach:
Remove the “%” operator for floating-point types. The operation is still be available via the C standard library fmod( ) function (which should be mapped to a Swiftier name, but that’s a separate proposal).

Alternative Considered:
Instead of binding “%” to fmod( ), it could be bound to remainder( ), which implements the IEEE 754 remainder operation; this is just like fmod( ), except instead of returning the remainder under truncating division, it returns the remainder of round-to-nearest division, meaning that if a and b are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0, b). This still has a large discontinuity, but the discontinuity is moved away from zero, which makes it much less troublesome (that’s why IEEE 754 standardized this operation):

  (swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like normal floating-point rounding

The downside to this alternative is that now % behaves totally differently for integer and floating-point data, and of course the division algorithm still doesn’t hold.

Proposal:
Remove the % operator for floating-point types in Swift 3. Add a warning in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

One of my long-term goals is for the standard library to provide (nearly) full support for all IEEE 754 required operations, which would include something similar to what you suggest. I’d like to address those bindings under a separate proposal, however, in order to keep this one simple.

– Steve

···

On Dec 18, 2015, at 4:19 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Hi Steve,
I like this idea because it removes a potential "gotcha" / unexpected behavior, and it will encourage people not to do stuff like this with floating-point numbers. (Somewhat like the clang analyzer's security.FloatLoopCounter <Available Checkers; tries to do.) The remainder() behavior makes sense to me.

However, I think it'd be nice for the standard library to continue exposing this behavior, if possible, rather than requiring the user to "import Darwin" or "import Glibc". Can these remain as methods on the built-in floating-point types, perhaps like .mod() and .remainder(), so they can be used if truly desired?

Jacob

On Fri, Dec 18, 2015 at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

Rationale:
While C and C++ do not provide the “%” operator for floating-point types, many newer languages do (Java, C#, and Python, to name just a few). Superficially this seems reasonable, but there are severe gotchas when % is applied to floating-point data, and the results are often extremely surprising to unwary users. C and C++ omitted this operator for good reason. Even if you think you want this operator, it is probably doing the wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does not hold for floating-point types, because a/b does not produce an integral value. If it did produce an integral value, it would need to be a bignum type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the division algorithm axiom, % would still be deeply flawed. Whereas people are generally used to modest rounding errors in floating-point arithmetic, because % is not continuous small errors are frequently enormously magnified with catastrophic results:

  (swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point; the actual value of “0.1” is 0.1000000000000000055511151231257827021181583404541015625. Other than that rounding, the entire computation is exact.]

Proposed Approach:
Remove the “%” operator for floating-point types. The operation is still be available via the C standard library fmod( ) function (which should be mapped to a Swiftier name, but that’s a separate proposal).

Alternative Considered:
Instead of binding “%” to fmod( ), it could be bound to remainder( ), which implements the IEEE 754 remainder operation; this is just like fmod( ), except instead of returning the remainder under truncating division, it returns the remainder of round-to-nearest division, meaning that if a and b are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0, b). This still has a large discontinuity, but the discontinuity is moved away from zero, which makes it much less troublesome (that’s why IEEE 754 standardized this operation):

  (swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like normal floating-point rounding

The downside to this alternative is that now % behaves totally differently for integer and floating-point data, and of course the division algorithm still doesn’t hold.

Proposal:
Remove the % operator for floating-point types in Swift 3. Add a warning in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

The same concerns apply to decimal. It makes sense to have the operation (for both binary and decimal floating-point) as “Type.remainder(a,b)” or a free function like "remainder(a, b)”, but I would prefer not to use the operator “%” for it because it behaves very differently from integer %, and in ways that are not at all obvious to most users.

– Steve

···

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising operation like this to have such short and inviting syntax. As asked downthread, have you given any thought into whether a Decimal type would support this operation?

Floating point numbers by their very nature are approximation represented in binary. Many numbers in Decimal cannot be represented exactly in decimal floating point format.
So after math calculations decimal number -> floating point binary -> repeating decimal numbers.

If decimal accuracy such as accounting, you should avoid using floats altogether and stick to Decimal type numbers (in java it is called BigDecimal, in Swift they have NSDecimalNumber I think which has it’s own issues.

···

On 2015-12-19, at 4:12:58, Stephen Canon via swift-evolution <swift-evolution@swift.org> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

Rationale:
While C and C++ do not provide the “%” operator for floating-point types, many newer languages do (Java, C#, and Python, to name just a few). Superficially this seems reasonable, but there are severe gotchas when % is applied to floating-point data, and the results are often extremely surprising to unwary users. C and C++ omitted this operator for good reason. Even if you think you want this operator, it is probably doing the wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does not hold for floating-point types, because a/b does not produce an integral value. If it did produce an integral value, it would need to be a bignum type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the division algorithm axiom, % would still be deeply flawed. Whereas people are generally used to modest rounding errors in floating-point arithmetic, because % is not continuous small errors are frequently enormously magnified with catastrophic results:

  (swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point; the actual value of “0.1” is 0.1000000000000000055511151231257827021181583404541015625. Other than that rounding, the entire computation is exact.]

Proposed Approach:
Remove the “%” operator for floating-point types. The operation is still be available via the C standard library fmod( ) function (which should be mapped to a Swiftier name, but that’s a separate proposal).

Alternative Considered:
Instead of binding “%” to fmod( ), it could be bound to remainder( ), which implements the IEEE 754 remainder operation; this is just like fmod( ), except instead of returning the remainder under truncating division, it returns the remainder of round-to-nearest division, meaning that if a and b are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0, b). This still has a large discontinuity, but the discontinuity is moved away from zero, which makes it much less troublesome (that’s why IEEE 754 standardized this operation):

  (swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like normal floating-point rounding

The downside to this alternative is that now % behaves totally differently for integer and floating-point data, and of course the division algorithm still doesn’t hold.

Proposal:
Remove the % operator for floating-point types in Swift 3. Add a warning in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Great, I’d prefer decimal and the float types to have a consistent interface where possible. +1 from me on the proposal.

-Chris

···

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising operation like this to have such short and inviting syntax. As asked downthread, have you given any thought into whether a Decimal type would support this operation?

The same concerns apply to decimal. It makes sense to have the operation (for both binary and decimal floating-point) as “Type.remainder(a,b)” or a free function like "remainder(a, b)”, but I would prefer not to use the operator “%” for it because it behaves very differently from integer %, and in ways that are not at all obvious to most users.

Agreed. +1 from me too. The `10.0 % 0.1` behavior is sufficiently
surprising that I think that justifies removal by itself.

I'm also in favor of adding mod and remainder as instance methods of the
floating point types, e.g. `10.0.mod(0.1)` (as opposed to static methods
or free functions).

-Kevin Ballard

···

On Fri, Dec 18, 2015, at 03:04 PM, Chris Lattner via swift-evolution wrote:

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com> >>> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift- >>> evolution@swift.org> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point
types.

I support removing this - it is actively harmful for a surprising
operation like this to have such short and inviting syntax. As
asked downthread, have you given any thought into whether a Decimal
type would support this operation?

The same concerns apply to decimal. It makes sense to have the
operation (for both binary and decimal floating-point) as
“Type.remainder(a,b)” or a free function like "remainder(a, b)”, but
I would prefer not to use the operator “%” for it because it behaves
very differently from integer %, and in ways that are not at all
obvious to most users.

Great, I’d prefer decimal and the float types to have a consistent
interface where possible. +1 from me on the proposal.

-August

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising operation like this to have such short and inviting syntax. As asked downthread, have you given any thought into whether a Decimal type would support this operation?

The same concerns apply to decimal. It makes sense to have the operation (for both binary and decimal floating-point) as “Type.remainder(a,b)” or a free function like "remainder(a, b)”, but I would prefer not to use the operator “%” for it because it behaves very differently from integer %, and in ways that are not at all obvious to most users.

Great, I’d prefer decimal and the float types to have a consistent interface where possible. +1 from me on the proposal.

Agreed. +1 from me too. The `10.0 % 0.1` behavior is sufficiently surprising that I think that justifies removal by itself.

I'm also in favor of adding mod and remainder as instance methods of the floating point types, e.g. `10.0.mod(0.1)` (as opposed to static methods or free functions).

Shouldn’t that be added to any Arithmetic type?

···

On 18 Dec, 2015, at 15:31, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
On Fri, Dec 18, 2015, at 03:04 PM, Chris Lattner via swift-evolution wrote:

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com <mailto:scanon@apple.com>> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:
On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Kevin Ballard

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

IntegerArithmeticType already defines the % operator, adding a mod()
method would just be duplication. If we have Float.remainder() then
arguably we might want an integral remainder too, although remainder
turns out to be rarely what people want (at least, for integral
calculations), which is why the % operator is mod.

-Kevin Ballard

···

On Fri, Dec 18, 2015, at 03:40 PM, August Joki wrote:

On 18 Dec, 2015, at 15:31, Kevin Ballard via swift-evolution <swift- >> evolution@swift.org> wrote:

On Fri, Dec 18, 2015, at 03:04 PM, Chris Lattner via swift- >> evolution wrote:

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com> >>>> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com> >>>>> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift- >>>>> evolution@swift.org> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point
types.

I support removing this - it is actively harmful for a surprising
operation like this to have such short and inviting syntax. As
asked downthread, have you given any thought into whether a
Decimal type would support this operation?

The same concerns apply to decimal. It makes sense to have the
operation (for both binary and decimal floating-point) as
“Type.remainder(a,b)” or a free function like "remainder(a, b)”,
but I would prefer not to use the operator “%” for it because it
behaves very differently from integer %, and in ways that are not
at all obvious to most users.

Great, I’d prefer decimal and the float types to have a consistent
interface where possible. +1 from me on the proposal.

Agreed. +1 from me too. The `10.0 % 0.1` behavior is sufficiently
surprising that I think that justifies removal by itself.

I'm also in favor of adding mod and remainder as instance methods of
the floating point types, e.g. `10.0.mod(0.1)` (as opposed to static
methods or free functions).

Shouldn’t that be added to any Arithmetic type?

We don't offer protocols that abstract over both integer and float arithmetic. They behave differently, and require different algorithms and interfaces. Floating-point types gaining a `mod` member does not necessarily mean integer types should get the same.

-Joe

···

On Dec 18, 2015, at 3:40 PM, August Joki via swift-evolution <swift-evolution@swift.org> wrote:

-August

On 18 Dec, 2015, at 15:31, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Fri, Dec 18, 2015, at 03:04 PM, Chris Lattner via swift-evolution wrote:

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com <mailto:scanon@apple.com>> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising operation like this to have such short and inviting syntax. As asked downthread, have you given any thought into whether a Decimal type would support this operation?

The same concerns apply to decimal. It makes sense to have the operation (for both binary and decimal floating-point) as “Type.remainder(a,b)” or a free function like "remainder(a, b)”, but I would prefer not to use the operator “%” for it because it behaves very differently from integer %, and in ways that are not at all obvious to most users.

Great, I’d prefer decimal and the float types to have a consistent interface where possible. +1 from me on the proposal.

Agreed. +1 from me too. The `10.0 % 0.1` behavior is sufficiently surprising that I think that justifies removal by itself.

I'm also in favor of adding mod and remainder as instance methods of the floating point types, e.g. `10.0.mod(0.1)` (as opposed to static methods or free functions).

Shouldn’t that be added to any Arithmetic type?

http://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/

···

On 2015-12-19, at 4:17:45, Craig Cruden <ccruden@novafore.com> wrote:

Floating point numbers by their very nature are approximation represented in binary. Many numbers in Decimal cannot be represented exactly in decimal floating point format.
So after math calculations decimal number -> floating point binary -> repeating decimal numbers.

If decimal accuracy such as accounting, you should avoid using floats altogether and stick to Decimal type numbers (in java it is called BigDecimal, in Swift they have NSDecimalNumber I think which has it’s own issues.

On 2015-12-19, at 4:12:58, Stephen Canon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

Rationale:
While C and C++ do not provide the “%” operator for floating-point types, many newer languages do (Java, C#, and Python, to name just a few). Superficially this seems reasonable, but there are severe gotchas when % is applied to floating-point data, and the results are often extremely surprising to unwary users. C and C++ omitted this operator for good reason. Even if you think you want this operator, it is probably doing the wrong thing in subtle ways that will cause trouble for you in the future.

The % operator on integer types satisfies the division algorithm axiom: If b is non-zero and q = a/b, r = a%b, then a = q*b + r. This property does not hold for floating-point types, because a/b does not produce an integral value. If it did produce an integral value, it would need to be a bignum type of some sort (the integral part of DBL_MAX / DBL_MIN, for example, has over 2000 bits or 600 decimal digits).

Even if a bignum type were returned, or if we ignore the loss of the division algorithm axiom, % would still be deeply flawed. Whereas people are generally used to modest rounding errors in floating-point arithmetic, because % is not continuous small errors are frequently enormously magnified with catastrophic results:

  (swift) 10.0 % 0.1
    // r0 : Double = 0.0999999999999995 // What?!

[Explanation: 0.1 cannot be exactly represented in binary floating point; the actual value of “0.1” is 0.1000000000000000055511151231257827021181583404541015625. Other than that rounding, the entire computation is exact.]

Proposed Approach:
Remove the “%” operator for floating-point types. The operation is still be available via the C standard library fmod( ) function (which should be mapped to a Swiftier name, but that’s a separate proposal).

Alternative Considered:
Instead of binding “%” to fmod( ), it could be bound to remainder( ), which implements the IEEE 754 remainder operation; this is just like fmod( ), except instead of returning the remainder under truncating division, it returns the remainder of round-to-nearest division, meaning that if a and b are positive, remainder(a,b) is in the range [-b/2, b/2] rather than [0, b). This still has a large discontinuity, but the discontinuity is moved away from zero, which makes it much less troublesome (that’s why IEEE 754 standardized this operation):

  (swift) remainder(1, 0.1)
    // r1 : Double = -0.000000000000000055511151231257827 // Looks like normal floating-point rounding

The downside to this alternative is that now % behaves totally differently for integer and floating-point data, and of course the division algorithm still doesn’t hold.

Proposal:
Remove the % operator for floating-point types in Swift 3. Add a warning in Swift 2.2 that points out the replacement fmod(a, b).

Thanks for your feedback,
– Steve

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

First of all great proposal, and thank you for trying to make it easier to
write safe code with Swift.

I feel like this proposal is more about human error than floating point
error.

I don't think this solves the precision issue. It makes it harder to use
fmod, but doesn't stop people wanting to, life finds a way. I completely
agree with your rationale, but if someone wants mod they'll use mod. The
question probably comes down to whether stackoverflow will tell them to use
fmod or remainder.

Regarding binding % to remainder, I think this is way too unexpected (a
much bigger gotcha):

3.0 % 4.0 == -1.0
3 % 4 == 3

I don't think we should change the binding to remainder.

If X.remainder(Y) is added to FloatingPointType I'm fairly neutral about
the proposal to remove % on floating point types.

I feel like the deprecation warning is basically teaching people that
instead of using % you now have to use fmod, you haven't gained anything
except making their code less readable. Perhaps an automatic migration is
better, or perhaps the warning should make it clear there are potential
precision issues.

The deprecation warning could be something like this:

Deprecated due to precision issues, renamed to 'fmod', consider rewriting
to use 'remainder' instead.

The issue with this I guess is that it is non-obvious how to change between
them, and I suspect attempts could just introduce similar or worse
precision issues.

It's almost a cliché on swift-evolution proposals, but I feel like my main
concerns here could almost be solved by a linter. Has Apple considered
adding built-in support for an easily customisable linter? There's probably
an overlap with clang's static analyser.

···

On Sat, Dec 19, 2015 at 12:10 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 18, 2015, at 3:40 PM, August Joki via swift-evolution < > swift-evolution@swift.org> wrote:

-August

On 18 Dec, 2015, at 15:31, Kevin Ballard via swift-evolution < > swift-evolution@swift.org> wrote:

On Fri, Dec 18, 2015, at 03:04 PM, Chris Lattner via swift-evolution wrote:

On Dec 18, 2015, at 3:03 PM, Stephen Canon <scanon@apple.com> wrote:

On Dec 18, 2015, at 5:57 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 18, 2015, at 1:12 PM, Stephen Canon via swift-evolution < > swift-evolution@swift.org> wrote:

Hi everybody —

I’d like to propose removing the “%” operator for floating-point types.

I support removing this - it is actively harmful for a surprising
operation like this to have such short and inviting syntax. As asked
downthread, have you given any thought into whether a Decimal type would
support this operation?

The same concerns apply to decimal. It makes sense to have the operation
(for both binary and decimal floating-point) as “Type.remainder(a,b)” or a
free function like "remainder(a, b)”, but I would prefer not to use the
operator “%” for it because it behaves very differently from integer %, and
in ways that are not at all obvious to most users.

Great, I’d prefer decimal and the float types to have a consistent
interface where possible. +1 from me on the proposal.

Agreed. +1 from me too. The `10.0 % 0.1` behavior is sufficiently
surprising that I think that justifies removal by itself.

I'm also in favor of adding mod and remainder as instance methods of the
floating point types, e.g. `10.0.mod(0.1)` (as opposed to static methods or
free functions).

Shouldn’t that be added to any Arithmetic type?

We don't offer protocols that abstract over both integer and float
arithmetic. They behave differently, and require different algorithms and
interfaces. Floating-point types gaining a `mod` member does not
necessarily mean integer types should get the same.

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Just for background and to save time, I’ll note out that Steve is one of Apple’s most respected numerics experts, and he’s more familiar with the details of how floating point, decimal, and other numeric representations work than just about anyone I’ve ever met.

FWIW-ly y’rs,

-Dave

···

On Dec 18, 2015, at 1:19 PM, Craig Cruden via swift-evolution <swift-evolution@swift.org> wrote:

Why 0.1 Does Not Exist In Floating-Point - Exploring Binary

On 2015-12-19, at 4:17:45, Craig Cruden <ccruden@novafore.com <mailto:ccruden@novafore.com>> wrote:

Floating point numbers by their very nature are approximation represented in binary. Many numbers in Decimal cannot be represented exactly in decimal floating point format.
So after math calculations decimal number -> floating point binary -> repeating decimal numbers.

If decimal accuracy such as accounting, you should avoid using floats altogether and stick to Decimal type numbers (in java it is called BigDecimal, in Swift they have NSDecimalNumber I think which has it’s own issues.

I am not a numerics expert…… just have run into people using floats in banking and brokerage systems to do calculation with decimal numbers too many times in my life…. I even spent a whole night in a bank vault with the system on conversion trying to figure out why it was out by a single penny :o

If I were not mentally unstable beforehand I would have gone insane over the misuse of floats, and the lack of languages having good decimal numeric types :p

···

On 2015-12-19, at 5:47:58, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 18, 2015, at 1:19 PM, Craig Cruden via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Why 0.1 Does Not Exist In Floating-Point - Exploring Binary

On 2015-12-19, at 4:17:45, Craig Cruden <ccruden@novafore.com <mailto:ccruden@novafore.com>> wrote:

Floating point numbers by their very nature are approximation represented in binary. Many numbers in Decimal cannot be represented exactly in decimal floating point format.
So after math calculations decimal number -> floating point binary -> repeating decimal numbers.

If decimal accuracy such as accounting, you should avoid using floats altogether and stick to Decimal type numbers (in java it is called BigDecimal, in Swift they have NSDecimalNumber I think which has it’s own issues.

Just for background and to save time, I’ll note out that Steve is one of Apple’s most respected numerics experts, and he’s more familiar with the details of how floating point, decimal, and other numeric representations work than just about anyone I’ve ever met.

FWIW-ly y’rs,

-Dave