Stephen Canon stephentyrone Lebanon, NH

Numerical APIs for Swift

PCG — C Implementation

The Swift Programming Language

Swift Algorithms is an open-source package of sequence and collection algorithms, along with their related types.

This package implements an atomics library for Swift, providing atomic operations for a variety of Swift types, including integers and pointer values. The goal is to enable intrepid developers to start building synchronization constructs directly in Swift.

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.

This is the version of LLDB that supports the Swift programming language & REPL.

issue commentapple/swift-numerics

It's no less accurate than the alternatives we have (as explained), not significantly slower, and yields considerable simplicity in the implementation. What's not to like?

comment created time in 8 days

issue commentapple/swift-numerics

Forgot to address z very nearly 0 😀

In this case, expMinusOne(x) cos(y) + cosMinusOne(y) becomes x*1 + y^2/2 = x, so there's no loss of accuracy in just using the expression we have.

comment created time in 8 days

issue commentapple/swift-numerics

Right, `exp(x) * cos(y) - 1` is about as good on that region, but not better. The expMinusOne alternative is always strictly worse. So there's no reason to use either of those on any region of the plane. You're always at least as well off using what we have now, and often better off.

In an additive expression with cancellation, the error comes only from the rounding of the terms that are cancelling. If you look at what we use currently:

``````expMinusOne(x) cos(y) + cosMinusOne(y)
``````

Assuming a good-quality host math library, expMinusOne, cos, and cosMinusOne are all computed with small relative error (ideally sub-ulp, a couple of ulp at worst). So we are evaluating an expression of the form a(1+δ₁) + b(1+δ₂) where a and b are the exact real values of (e^x-1)cos(y) and cos(y)-1, respectively and the deltas are bounded by a small multiple of epsilon. Because we're interested in the region close to the critical curve, the addition is computed exactly, so the result is precisely Re(exp(z)-1) + δ₁a + δ₂b, and so the absolute error is bounded by (|a|+|b|)kε for some small k.

Note that this analysis holds for all of the expressions you consider, except the "dual" factorization; the way we minimize the error bound is to minimize `|a|` and `|b|`. Among the alternatives considered, the one in use minimizes them for all input values, not just in some region. There's a small note to make for exp(x)cos(y) - 1, because 1 is exact and therefore does not contribute to the error, but that buys you at most a bit or two in practice in this case, hardly worth the added complexity when you still have essentially no good bits in the result (it would be likely to be worth using in an arbitrary-precision implementation, however).

comment created time in 8 days

issue commentapple/swift-numerics

Second, the problem curve approaches cos(y) = 0 as x increases, so cosMinusOne(y) is actively harmful in that region for large x.

Well, no, because the alternative factorization (`exp(x) cosMinuxOne(y) + expMinusOne(x)`) has terms that get arbitrarily large, while the terms in the factorization we're using stay bounded near 1. So it's not what we want, but it's far better than the only alternative we have available currently.

comment created time in 8 days

pull request commentapple/swift-numerics

Hi @markuswntr, can you update this to point against main instead of master? Thanks.

markuswntr

comment created time in 9 days

push eventstephentyrone/swift-numerics

commit sha 957187037f3df905502e3c392976ac561a9a4939

WIP on log(onePlus:) The error is still slightly larger than it should be close to the critical circle when x ~ -0.5, so there's a rounding that isn't anticipated to be accounted for somewhere. However, the normwise accuracy is excellent, and the test refactoring is good. Drops ArgumentParser dependency, which we don't really need--it was more for fun--and is probably good to avoid possible numerics->algorithms->argument parser circular dependencies in the future.

push time in 9 days

delete branch stephentyrone/swift-algorithms

delete branch : use-the-package-luke

delete time in 9 days

pull request commentapple/swift-algorithms

I think it makes sense because: (a) We want people to use SN for the elementary functions--it's the "blessed" package for those APIs. If Numerics needed Cycle or whatever, I would expect to pull in Algorithms for it. (b) In this particular case, I expect that Algorithms will want other pieces of Numerics in the future (especially stuff around RNGs), so I believe that you'll end up with this dependency sooner or later anyway.

stephentyrone

comment created time in 10 days

push eventapple/swift

When parsing floating-point from String, underflow to 0, overflow to infinity (#34339) Previously, overflow and underflow both caused this to return `nil`, which causes several problems: * It does not distinguish between a large but valid input and a malformed input. `Float("3.402824e+38")` is perfectly well-formed but returns nil * It differs from how the compiler handles literals. As a result, `Float(3.402824e+38)` is very different from `Float("3.402824e+38")` * It's inconsistent with Foundation Scanner() * It's inconsistent with other programming languages This is exactly the same as #25313 Fixes rdar://problem/36990878

push time in 10 days

PR merged apple/swift

Previously, overflow and underflow both caused this to return `nil`, which causes several problems:

• It does not distinguish between a large but valid input and a malformed input. `Float("3.402824e+38")` is perfectly well-formed but returns nil
• It differs from how the compiler handles literals. As a result, `Float(3.402824e+38)` is very different from `Float("3.402824e+38")`
• It's inconsistent with Foundation Scanner()
• It's inconsistent with other programming languages

Note: This is exactly the same as #25313 by @stephentyrone

Fixes rdar://problem/36990878

+9 -22

3 changed files

tbkka

pr closed time in 10 days

pull request commentapple/swift

tbkka

comment created time in 11 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventstephentyrone/swift-algorithms

commit sha 73c4a2156347366818ee6f7914bfdebc1239b079

Add the magic plist file to make xcode render the markdown guides. (#27)

Use Swift Numerics for elementary functions instead of Darwin/Glibc/etc Also lets us use root instead of exp(log(x)/k), which is more accurate in some cases (but mainly is just nicer to read). The downside is that this introduces a dependency for Algorithms, where previously it had none. I think that Numerics is an OK thing to depend on (especially for these functions, which are available from 0.0.1), but it _is_ a _massive increase_ in the number of dependencies that the package has, and we should give that some thought.

push time in 13 days

PR opened apple/swift-algorithms

This drops the canImport darwin/glibc/etc dance, which is ugly, and lets us use `root` instead of `exp(log(x)/k)`, which is more accurate in some cases (but mainly is just nicer to read).

The downside is that this introduces a dependency for Algorithms, where previously it had none. I think that Numerics is an OK thing to depend on (especially for these functions, which are available from 0.0.1), but it is a massive increase in the number of dependencies that the package has, and we should give that some thought.

+17 -19

0 comment

3 changed files

pr created time in 13 days

create barnchstephentyrone/swift-algorithms

created branch time in 13 days

delete branch stephentyrone/swift-algorithms

delete branch : render-markdown-in-xcode

delete time in 13 days

create barnchstephentyrone/swift-algorithms

created branch time in 13 days

delete branch stephentyrone/swift-algorithms

delete branch : render-markdown-in-xcode

delete time in 13 days

PR opened apple/swift-algorithms

The downside to doing this is that it makes the files more inconvenient to edit, since Xcode doesn't let you toggle out of the rendered view easily, but I think it makes sense on the premise that these are read much more often than edited. I have this flag set in Swift Numerics for this reason.

+5 -0

0 comment

1 changed file

pr created time in 13 days

create barnchstephentyrone/swift-algorithms

created branch time in 13 days

Swift Algorithms is an open-source package of sequence and collection algorithms, along with their related types.

fork in 13 days

push eventapple/swift-numerics

commit sha 0ee972ff9227b05207fa3f9b55aefe332c7fba1b

build: enable building `Numerics` on Darwin with CMake The `-enable-force-autolink` flag is not supported on Darwin. Fortunately, it is also unnecessary to work around the SR on Darwin. Simply exclude the flag when targeting Darwin.

commit sha 874c7674243cae891ed2dd5c8750abe4249337e9

Merge pull request #159 from compnerd/autolink build: enable building `Numerics` on Darwin with CMake

push time in 15 days

PR merged apple/swift-numerics

The `-enable-force-autolink` flag is not supported on Darwin. Fortunately, it is also unnecessary to work around the SR on Darwin. Simply exclude the flag when targeting Darwin.

+1 -1

0 comment

1 changed file

compnerd

pr closed time in 15 days

PullRequestReviewEvent

pull request commentJuliaLang/julia

@simonbyrne Swift doesn't document Float16 specifically, but as a matter of policy we never carry excess precision in floating-point. That leaves some optimizations on the table (and can give less accurate results), but helps with portability and predictability.

comment created time in 21 days

startedapple/swift-atomics

started time in 21 days

This package implements an atomics library for Swift, providing atomic operations for a variety of Swift types, including integers and pointer values. The goal is to enable intrepid developers to start building synchronization constructs directly in Swift.

fork in 21 days

startedapple/swift-algorithms

started time in 21 days

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentapple/swift-atomics

` SWIFTATOMIC_DEFINE_TYPE(COMPLEX, DoubleWord, _sa_dword, uint64_t) #error "Unsupported intptr_t bit width" #endif // __INTPTR_WIDTH -extern void _sa_retain_n(void *object, uint32_t n);-extern void _sa_release_n(void *object, uint32_t n);+static inline void _sa_retain_n(void *object, uint32_t n) {`

You might crib `#define HEADER_SHIM static inline __attribute__((__always_inline__))` from Swift Numerics for this purpose.

lorentey

comment created time in 23 days

pull request commentapple/swift

Ship it.

xwu

comment created time in 24 days

Pull request review commentapple/swift

` extension BinaryInteger {     return self.magnitude % other.magnitude == 0   } +  /// Returns `true` if this value is a power of the given base, and `false`+  /// otherwise.+  ///+  /// For two integers *a* and *b*, *a* is a power of *b* if *a* is equal to+  /// any repeated multiplication of *b*. For example, *16* is a power of *2*+  /// because _16 = 2 * 2 * 2 * 2_. *-27* is a power of *-3* because+  /// _-27 = -3 * -3 * -3_. *1* is a power of everything because any number to+  /// the zero power is one.`

/// One is a power of everything because any number to the zero power is one.

This is not exactly right; one is a power of everything because the empty product is one because one is the multiplicative identity. "Any number to the zero power is one" also follows from the convention for the empty product. I'm not sure how far down this rabbit hole the comment actually needs to go, however =)

dingobye

comment created time in a month

PullRequestReviewEvent

Pull request review commentapple/swift

` extension BinaryInteger {     return self.magnitude % other.magnitude == 0   } +  /// Returns `true` if this value is a power of the given base, and `false`+  /// otherwise.+  ///+  /// For two integers *a* and *b*, *a* is a power of *b* if *a* is equal to+  /// any repeated multiplication of *b*. For example, *16* is a power of *2*+  /// because _16 = 2 * 2 * 2 * 2_. *-27* is a power of *-3* because+  /// _-27 = -3 * -3 * -3_. *1* is a power of everything because any number to+  /// the zero power is one.+  ///+  /// For the corner case where base is zero, `x.isPower(of: 0)` is `true` if+  /// `x` is either zero or one, and `false` otherwise.+  ///+  /// - Parameter base: The base value to test.+  @inlinable+  public func isPower(of base: Self) -> Bool {+    // Fast path when base is one of the common cases.+    if base == 2 { return self._isPowerOfTwo }+    if base == 10 { return self._isPowerOfTen }+    if base._isPowerOfTwo { return self._isPowerOf(powerOfTwo: base) }+    // Slow path for other bases.+    return self._slowIsPower(of: base)+  }++  /// Returns `true` iff `self` is a power of two.+  ///+  /// This serves as a fast path for `isPower(of:)` when the input base is two.+  @inlinable+  internal var _isPowerOfTwo: Bool {+    let words = self.words+    guard !words.isEmpty else { return false }++    // If the value is represented in a single word, perform the classic check.+    if words.count == 1 {+      return self > 0 && self & (self - 1) == 0+    }++    // Return false if it is negative.  Here we only need to check the most+    // significant word (i.e. the last element of `words`).+    if Self.isSigned && Int(bitPattern: words.last!) < 0 {+      return false+    }++    // Check if there is exactly one non-zero word and it is a power of two.+    var found = false+    for word in words {+      if word != 0 {+        if found || word & (word - 1) != 0 { return false }+        found = true+      }+    }+    return found+  }++  /// Returns `true` iff `self` is a power of the given `base`, which itself is+  /// a power of two.+  ///+  /// This serves as a fast path for `isPower(of:)` when the input base itself+  /// is a power of two.+  @inlinable+  internal func _isPowerOf(powerOfTwo base: Self) -> Bool {+    _precondition(base._isPowerOfTwo)+    guard self._isPowerOfTwo else { return false }+    return self.trailingZeroBitCount.isMultiple(of: base.trailingZeroBitCount)+  }++  /// Returns `true` iff `self` is a power of ten.+  ///+  /// This serves as a fast path for `isPower(of:)` when the input base is ten.+  @usableFromInline+  internal var _isPowerOfTen: Bool {+    let exponent = self.trailingZeroBitCount+    switch exponent {+    case 0:  return self == 1 as UInt8+    case 1:  return self == 10 as UInt8+    case 2:  return self == 100 as UInt8+    case 3:  return self == 1000 as UInt16+    case 4:  return self == 10000 as UInt16+    case 5:  return self == 100000 as UInt32+    case 6:  return self == 1000000 as UInt32+    case 7:  return self == 10000000 as UInt32+    case 8:  return self == 100000000 as UInt32+    case 9:  return self == 1000000000 as UInt32+    case 10: return self == 10000000000 as UInt64+    case 11: return self == 100000000000 as UInt64+    case 12: return self == 1000000000000 as UInt64+    case 13: return self == 10000000000000 as UInt64+    case 14: return self == 100000000000000 as UInt64+    case 15: return self == 1000000000000000 as UInt64+    case 16: return self == 10000000000000000 as UInt64+    case 17: return self == 100000000000000000 as UInt64+    case 18: return self == 1000000000000000000 as UInt64+    case 19: return self == 10000000000000000000 as UInt64+    default:+      // If this is 64-bit or less we can't have a higher power of 10+      if self.bitWidth <= 64 { return false }++      // Quickly check if parts of the bit pattern fits the power of 10.+      //+      // 10^0                                     1+      // 10^1                                  1_01_0+      // 10^2                               1_10_01_00+      // 10^3                             111_11_01_000+      // 10^4                          100111_00_01_0000+      // 10^5                        11000011_01_01_00000+      // 10^6                      1111010000_10_01_000000+      // 10^7                   1001100010010_11_01_0000000+      // 10^8                 101111101011110_00_01_00000000+      // 10^9               11101110011010110_01_01_000000000+      // 10^10           10010101000000101111_10_01_0000000000+      // ...+      // Column 1 is some "gibberish", which cannot be checked easily+      // Column 2 is always the last two bits of the exponent+      // Column 3 is always 01+      // Column 4 is the trailing zeros, in equal number to the exponent value+      //+      // We check if Column 2 matches the last two bits of the exponent and+      // Column 3 matches 0b01.+      guard (self >> exponent)._lowWord & 0b1111 ==+        ((exponent << 2) | 0b01) & 0b1111 else { return false }++      // Now time for the slow path.+      return self._slowIsPower(of: 10)+    }+  }++  /// Returns `true` iff `self` is a power of the given `base`.+  ///+  /// This serves as the slow path for `isPower(of:)`; it is based on a generic+  /// implementation that works for any input `base`.+  @usableFromInline+  internal func _slowIsPower(of base: Self) -> Bool {+    // If self is 1 (i.e. any base to the zero power), return true.+    if self == 1 { return true }++    // Here if base is 0, 1 or -1, return true iff self equals base.+    if base.magnitude <= 1 { return self == base }++    // At this point, we have base.magnitude >= 2. Repeatedly perform+    // multiplication by a factor of base, and check if it can equal self.`

The comment here doesn't match the implementation. Please update.

dingobye

comment created time in a month

PullRequestReviewEvent

pull request commentapple/swift

dingobye

comment created time in a month

pull request commentapple/swift

@swift-ci test

dingobye

comment created time in a month

push eventapple/swift-numerics

commit sha 0f8ef268d01fe0e77dcb8da46a5aa64590aa9abf

commit sha 3d7462a834b58b10b1af4b1a1e0eeb784835681b

commit sha e07b39453b883f554f1154eedeecd948c11d5888

Swap availability for #if check on Float16.

commit sha 6b24333510e9044cf4716a07bed65eeed6bc6393

Merge pull request #157 from stephentyrone/float16-availability-redux Swap availability for #if check on Float16.

Initial pass over implementing ElementaryFunctions for Complex.

commit sha 6d9de5d4ce8c21749f9fb1cdedea39615738dfd7

Updates to resync with changes on master. - Renamed scaled/unscaled to multiplied/divided - Adopted new Real module name - Adopted expMinusOne, log(onePlus:) names

commit sha 2179fa1f828b60f3699756d461aa16dc17f85148

Rename some tests to reflect what they actually do.

commit sha 09beddce470c9ccc9ae57e17f582f1d8570f9ac8

WIP on complex elementary functions. Rebased on master, then cleaned up exp and expm1. New algorithm for expm1 that gives good componentwise accuracy as well as normwise. I _think_ that this is fundamentally new; I can't recall seeing it anywhere before, though it's simple enough that I expect someone has thought of it before. Also beefed up testing for exp and expm1. Left sketches of log/pow/sqrt/root in place, though I'm still working on filling out the testing for those.

commit sha 5f408c012154b73112b9db837d29b84fd00ff453

Cleanup.

commit sha 4798b682032a32a8c8dc2cd1993f1dd4776c4b5c

Refactor test support to remove XCTest dependency.

commit sha efbe11a73418f6707b58086ef8e35044b8793c8a

Fix typo expMinusOne/exp in near-overflow check.

commit sha 0b2dc469eda2c91c0091f56be7b1eaa4d5159b86

Moved sampling-based tests out of unit and into an exectuable target.

commit sha 1cbb94ac717415450ee64ed0cd0b040d5e108460

First pass over hyperbolics and trig functions for Complex.

commit sha ed8fc21f1e74633dd6a34a38110decdd6f0b9f02

Add TODO for algorithmic improvements on tanh.

commit sha af19aec6ff4582378f178909aa364c32381bc894

WIP

commit sha 209a794eb63c70e96d8c9deea96ffcfb0066a3e4

Initial implementation of log(onePlus:)

commit sha 754cbd9e2e16799afcc3690a16eb125d97343ab5

Merge branch 'complex-elfns' of github.com:stephentyrone/swift-numerics into complex-elfns

push time in a month

push eventstephentyrone/swift-numerics

commit sha 0f8ef268d01fe0e77dcb8da46a5aa64590aa9abf

commit sha 3d7462a834b58b10b1af4b1a1e0eeb784835681b

commit sha e07b39453b883f554f1154eedeecd948c11d5888

Swap availability for #if check on Float16.

commit sha 6b24333510e9044cf4716a07bed65eeed6bc6393

Merge pull request #157 from stephentyrone/float16-availability-redux Swap availability for #if check on Float16.

Initial pass over implementing ElementaryFunctions for Complex.

commit sha 6d9de5d4ce8c21749f9fb1cdedea39615738dfd7

Updates to resync with changes on master. - Renamed scaled/unscaled to multiplied/divided - Adopted new Real module name - Adopted expMinusOne, log(onePlus:) names

commit sha 2179fa1f828b60f3699756d461aa16dc17f85148

Rename some tests to reflect what they actually do.

commit sha 09beddce470c9ccc9ae57e17f582f1d8570f9ac8

WIP on complex elementary functions. Rebased on master, then cleaned up exp and expm1. New algorithm for expm1 that gives good componentwise accuracy as well as normwise. I _think_ that this is fundamentally new; I can't recall seeing it anywhere before, though it's simple enough that I expect someone has thought of it before. Also beefed up testing for exp and expm1. Left sketches of log/pow/sqrt/root in place, though I'm still working on filling out the testing for those.

commit sha 5f408c012154b73112b9db837d29b84fd00ff453

Cleanup.

commit sha 4798b682032a32a8c8dc2cd1993f1dd4776c4b5c

Refactor test support to remove XCTest dependency.

commit sha efbe11a73418f6707b58086ef8e35044b8793c8a

Fix typo expMinusOne/exp in near-overflow check.

commit sha 0b2dc469eda2c91c0091f56be7b1eaa4d5159b86

Moved sampling-based tests out of unit and into an exectuable target.

commit sha 1cbb94ac717415450ee64ed0cd0b040d5e108460

First pass over hyperbolics and trig functions for Complex.

commit sha ed8fc21f1e74633dd6a34a38110decdd6f0b9f02

Add TODO for algorithmic improvements on tanh.

commit sha af19aec6ff4582378f178909aa364c32381bc894

WIP

commit sha 209a794eb63c70e96d8c9deea96ffcfb0066a3e4

Initial implementation of log(onePlus:)

commit sha 754cbd9e2e16799afcc3690a16eb125d97343ab5

Merge branch 'complex-elfns' of github.com:stephentyrone/swift-numerics into complex-elfns

push time in a month

pull request commentapple/swift

Seems reasonable to me!

NevinBR

comment created time in a month

pull request commentapple/swift

Okay, I’ve added benchmarks for those ranges.

Thank you!

I also notice that most of these functions are identical except for the range, the RNG, and the number of iterations. So I’m thinking of making a helper function to avoid the repetition. I’m not super familiar with benchmarking though—is there any reason I shouldn’t do that?

I think that's perfectly reasonable. Do make sure that it doesn't fall off a performance cliff, since we want to be benchmarking the RNG and transform, not the benchmark overhead, but I don't expect that it will.

NevinBR

comment created time in a month

delete branch stephentyrone/swift

delete branch : overflow-underflow-nonnil

delete time in a month

PR closed apple/swift

Reviewers

All other floating-point inits round out-of-range inputs to the nearest representable value. The conversions from string currently do not match this behavior, and return nil instead. This change makes it so only invalid character sequences produce nil; with overflow producing infinity and underflow producing zero, matching the behavior of literals.

+8 -20

3 changed files

stephentyrone

pr closed time in a month

delete branch stephentyrone/swift-numerics

delete branch : master

delete time in a month

create barnchstephentyrone/swift-numerics

created branch time in a month

create barnchapple/swift-numerics

created branch time in a month

push eventstephentyrone/swift-numerics

commit sha 2d2c144808c13bd2e9a98476cc87de8e75306f03

Fixup tests borked by bad merge.

push time in a month

push eventstephentyrone/swift-numerics

commit sha 6d396dfb4497e2d19c15ab671b6dd1dc47f7c0e5

First pass over hyperbolics and trig functions for Complex.

commit sha beb85076a5ed5562c09474f273665af291e1b0fc

Add TODO for algorithmic improvements on tanh.

commit sha a76404b263a8ddba34a9f3e91f7271c0d2c13777

WIP

commit sha 0f02c6585465356cf5ba4d0937221dcc81fd7690

Initial implementation of log(onePlus:)

commit sha 21282166fa3bc0102fb2454863c6ddeab988fed1

Merge pull request #152 from stephentyrone/complex-trig First pass over hyperbolics and trig functions for Complex.

push time in a month

push eventstephentyrone/swift-numerics

commit sha 36bc51fc293f2a24ef5089b4276fbd964438d32a

commit sha 4deabc9cba7850d2aa0d186fc9deab51b13490f4

Swap availability for #if check on Float16.

commit sha 51db622dcaf5578a10c21170125d5f8104eac3f2

Initial implementation of log(onePlus:)

commit sha e6999a1d4185393072a06ce514da9e9b96777096

Fix typo.

Complex.log with good componentwise relative error Trying to better understand the problems with log1p led me to realize that they exist for log as well in the complex plane. So I reworked log using a novel approach, and now I'm going to circle around to log1p once more.

commit sha 6fb9e4bfa3eb892f912c9e95d0975ddd56f0b122

WIP on complex elementary functions. Rebased on master, then cleaned up exp and expm1. New algorithm for expm1 that gives good componentwise accuracy as well as normwise. I _think_ that this is fundamentally new; I can't recall seeing it anywhere before, though it's simple enough that I expect someone has thought of it before. Also beefed up testing for exp and expm1. Left sketches of log/pow/sqrt/root in place, though I'm still working on filling out the testing for those.

Fix typo expMinusOne/exp in near-overflow check.

commit sha 9671911ee640e2f25c7910481274166b28fd6d7a

Initial implementation of log(onePlus:)

commit sha 4225c8b1d9fc771061cf67d08f0d8ac40822d806

Fix typo.

commit sha 4494cea12b2e2343e32ed3dc055eb8a64a5dd136

Further WIP on log(onePlus:), more to be done.

push time in a month

delete branch stephentyrone/swift-numerics

delete branch : complex-trig

delete time in a month

push eventapple/swift-numerics

commit sha 6d396dfb4497e2d19c15ab671b6dd1dc47f7c0e5

First pass over hyperbolics and trig functions for Complex.

commit sha beb85076a5ed5562c09474f273665af291e1b0fc

Add TODO for algorithmic improvements on tanh.

commit sha a76404b263a8ddba34a9f3e91f7271c0d2c13777

WIP

commit sha 0f02c6585465356cf5ba4d0937221dcc81fd7690

Initial implementation of log(onePlus:)

commit sha 21282166fa3bc0102fb2454863c6ddeab988fed1

Merge pull request #152 from stephentyrone/complex-trig First pass over hyperbolics and trig functions for Complex.

push time in a month

PR merged apple/swift-numerics

Also refactors exp and expMinusOne fairly incidentally.

+241 -75

0 comment

2 changed files

stephentyrone

pr closed time in a month

issue commentapple/swift-numerics

Yup. This is a problem that no one has a good solution to yet. I have a few ideas, but haven't had time to sort through them.

As you suggest, the cosm1 factorization is strictly better than the expm1 factorization (it at least gets an absolute error bound for this case, which is why I'm using it).

comment created time in a month

issue commentapple/swift-system

@Snowy1803 Note that System is included in those versions of the OSes (https://developer.apple.com/documentation/system).

BasThomas

comment created time in a month

Pull request review commentapple/swift-numerics

` extension Complex /*: ElementaryFunctions */ {     // If z is zero or infinite, the phase is undefined, so the result is     // the single exceptional value.     guard z.isFinite && !z.isZero else { return .infinity }-    // Otherwise, try computing lengthSquared; if the result is normal,-    // we can just take its log to get the real part of the result.-    let r2 = z.lengthSquared+    // Having eliminated non-finite values and zero, the imaginary part is+    // easy; it's just the phase, which is always computed with good+    // relative accuracy via atan2.     let θ = z.phase-    if r2.isNormal { return Complex(.log(r2)/2, θ) }-    // z is finite, but z.lengthSquared is not normal. Rescale and recompute.-    let w = z.divided(by: z.magnitude)-    return Complex(.log(z.magnitude) + .log(w.lengthSquared)/2, θ)-  }-  -  @inlinable-  public static func log(onePlus z: Complex) -> Complex {-    // Nevin proposed the idea for this implementation on the Swift forums:-    // https://forums.swift.org/t/elementaryfunctions-compliance-for-complex/37903/3+    // The real part of the result is trickier. In exact arithmetic, the+    // real part is just log |z|--many implementations of complex functions+    // simply use this expression as is. However, there are two problems+    // lurking here:+    //+    //   - length can overflow even when log(z) is finite.+    //+    //   - when length is close to 1, catastrophic cancellation is hidden+    //     in this expression. Consider, e.g. z = 1 + δi for small δ.+    //+    //     Because δ ≪ 1, |z| rounds to 1, and so log |z| produces zero.+    //     We can expand using Taylor series to see that the result should+    //     be:+    //+    //         log |z| = log √(1 + δ²)+    //                 = log(1 + δ²)/2+    //                 = δ²/2 + O(δ⁴)+    //+    //     So naively using log |z| results in a total loss of relative+    //     accuracy for this case. Note that this is _not_ constrained near+    //     a single point; it occurs everywhere close to the circle |z| = 1.+    //+    //     Note that this case still _does_ deliver a result with acceptable+    //     relative accuracy in the complex norm, because+    //+    //         Im(log z) ≈ δ ≫ δ²/2 ≈ Re(log z).     //-    // Here's a quick explainer on why it works: in exact arithmetic,+    // There are a number of ways to try to tackle this problem. I'll begin+    // with a simple one that solves the first issue, and _sometimes_ the+    // second, then analyze when it doesn't work for the second case.     //-    //      log(1+z) = (log |1+z|, atan2(y, 1+x))+    // To handle very large arguments without overflow, the standard+    // approach is to _rescale_ the problem. We can do this by finding+    // whichever of x and y has greater magnitude, and dividing through+    // by it. You can think of this as changing coordinates by reflections+    // so that we get a new value w = u + iv with |w| = |z| (and hence+    // Re(log w) = Re(log z), and 0 ≤ u, 0 ≤ v ≤ u.+    let u = max(z.x.magnitude, z.y.magnitude)+    let v = min(z.x.magnitude, z.y.magnitude)+    // Now expand out log |w|:     //-    // where x and y are the real and imaginary parts of z, respectively.+    //     log |w| = log(u² + v²)/2+    //             = log u + log(onePlus: (v/u)²)/2     //-    // The first thing to note is that the expression for the imaginary-    // part works fine as is. If cancellation occurs (because x ≈ -1),-    // then 1+x is exact, and so we have good componentwise relative-    // accuracy. Otherwise, x is bounded away from -1 and 1+x has good-    // relative accuracy, and therefore so does atan2(y, 1+x).+    // This looks promising! It handles overflow well, because log(u) is+    // finite for every finite u, and we have 0 ≤ v/u ≤ 1, so the second+    // term is bounded by 0 ≤ log(1 + (v/u)²)/2 ≤ (log 2)/2. It also+    // handles the example I gave above well: we have u = 1, v = δ, and     //-    // So the real part is the hard part (no surprise, just like expPlusOne).-    // Nevin's clever idea is simply to take advantage of the expansion:+    //     log(1) + log(onePlus: δ²)/2 = 0 + δ²/2     //-    //     Re(log 1+z) = (log 1+z + Conj(log 1+z))/2+    // as expected.     //-    // Log commutes with conjugation, so this becomes:+    // Unfortunately, it does not handle all points close to the unit+    // circle so well; it's easy to see why if we look at the two terms+    // that contribute to the result. Cancellation occurs when the result+    // is close to zero and the terms have opposing signs. By construction,+    // the second term is always positive, so the easiest observation is+    // that cancellation is only a problem for u < 1 (because otherwise+    // log u is also positive, and there can be no cancellation).+    //+    // We are not trying for sub-ulp accuracy, just a good relative error+    // bound, so for our purposes it suffices to have log u dominate the+    // result:+    if u >= 1 || u >= RealType._mulAdd(u,u,v*v) {+      let r = v / u+      return Complex(.log(u) + .log(onePlus: r*r)/2, θ)+    }+    // Here we're in the tricky case; cancellation is likely to occur.+    // Instead of the factorization used above, we will want to evaluate+    // log(onePlus: u² + v² - 1)/2. This all boils down to accurately+    // evaluating u² + v² - 1. To begin, calculate both squared terms+    // as exact head-tail products (u is guaranteed to be well scaled,+    // v may underflow, but if it does it doesn't matter, the u term is+    // all we need).+    let (a,b) = Augmented.twoProdFMA(u, u)+    let (c,d) = Augmented.twoProdFMA(v, v)+    // It would be nice if we could simply use a - 1, but unfortunately+    // we don't have a tight enough bound to guarantee that that expression+    // is exact; a may be as small as 1/4, so we could lose a single bit+    // to rounding if we did that.+    var (s,e) = Augmented.fastTwoSum(-1, a)+    // Now we are ready to assemble the result. If cancellation happens,+    // then |c| > |e| > |b|, |d|, so this assembly order is safe. It's+    // also possible that |c| and |d| are small, but if that happens then+    // there is no significant cancellation, and the exact assembly doesn't+    // matter.+    s = (s + c) + e + b + d+    return Complex(.log(onePlus: s)/2, θ)+  }+  +  @inlinable+  public static func log(onePlus z: Complex) -> Complex {+    // log(1+z) is basically only hard to compute close to the circle+    // |1+z| = 1; inside the circle the real part of the log is negative;+    // outside it positive. On the circle (which passes through 0, -1±i,+    // and -2), it is zero, so we need to be careful about cancellation.+    //+    // To begin, let's just see what domain z actually lies in.+    let r2 = (1 + z).lengthSquared+    // If |1+z| <= ½, we're well inside the circle (and close enough+    // to -1 that 1 + z is computed exactly. If |1+z| => 2, we're far+    // enough outside the circle that we can simply use log(1+z)--1+z+    // may round, but that rounding doesn't cause problematic error in+    // the final result.+    if 2*r2 <= 1 && !(r2 < 2) { return log(1 + z) }+    // Here we're in the annulus ½ < |1+z| < 2, so we need to be careful+    // about cancellation. Instead of computing the real part as+    // log(|1+z|), we use log(onePlus: 2x + |z|²)/2. This is a cute trick+    // that Nevin suggested on the swift forums, which works because:     //     //     Re(log 1+z) = (log 1+z + log 1+z̅)/2-    //                 = log((1+z)(1+z̅)/2+    //                 = log((1+z)(1+z̅))/2     //                 = log(1+z+z̅+zz̅)/2     //-    // This behaves well close to zero, because the z+z̅ term dominates-    // and is computed exactly. Away from zero, cancellation occurs near-    // the circle x(x+2) + y^2 = 1, but everywhere along this curve we-    // have |Im(log 1+z)| >= π/2, so the relative error in the complex-    // norm is well-controlled. We can take advantage of FMA to further-    // reduce the cancellation error and recover a good error bound.-    //-    // The other common implementation choice for log1p is Kahan's trick:+    // https://forums.swift.org/t/elementaryfunctions-compliance-for-complex/37903/3     //-    //     w := 1+z-    //     return z/(w-1) * log(w)+    // So now we just need to compute z+z̅+zz̅ == x(2+x) + y², carefully.+    // 2+x can round; we have -3 < x < 1, so we can compute a head-tail+    // sum easily. It's easiest to see why this is exact with a two-case+    // analysis:     //-    // But this actually doesn't do as well as Nevin's approach does,-    // and requires a complex division, which we want to avoid when we-    // can do so.-    var a = 2*z.x-    // We want to add the larger term first (contra usual guidance for-    // floating-point error optimization), because we're optimizing for-    // the catastrophic cancellation case; when that happens adding the-    // larger term via FMA is always exact. When cancellation doesn't-    // happen, the simple relative error bound carries through the-    // rest of the computation.-    let large = max(z.x.magnitude, z.y.magnitude)-    let small = min(z.x.magnitude, z.y.magnitude)-    a.addProduct(large, large)-    a.addProduct(small, small)-    // If r2 overflowed, then |z| ≫ 1, and so log(1+z) = log(z).-    guard a.isFinite else { return log(z) }-    // Unlike log(z), we do not need to worry about what happens if a-    // underflows.-    return Complex(-      RealType.log(onePlus: a)/2,-      RealType.atan2(y: z.y, x: 1+z.x)-    )+    //  	If x is in [-3, -1], everything is exact because of Sterbenz.+    //    If x is in (-1, +1], 2 + x will generally round, but 2 > |x|,+    //      so no sorting is needed to order the tail computation.+    return log(1+z)`

(i.e. reviewing log(onePlus:) is wasted effort, I just hacked a merge together so it would build and I could do some testing on log( ).)

stephentyrone

comment created time in a month

PullRequestReviewEvent

Pull request review commentapple/swift-numerics

` extension Complex /*: ElementaryFunctions */ {     // If z is zero or infinite, the phase is undefined, so the result is     // the single exceptional value.     guard z.isFinite && !z.isZero else { return .infinity }-    // Otherwise, try computing lengthSquared; if the result is normal,-    // we can just take its log to get the real part of the result.-    let r2 = z.lengthSquared+    // Having eliminated non-finite values and zero, the imaginary part is+    // easy; it's just the phase, which is always computed with good+    // relative accuracy via atan2.     let θ = z.phase-    if r2.isNormal { return Complex(.log(r2)/2, θ) }-    // z is finite, but z.lengthSquared is not normal. Rescale and recompute.-    let w = z.divided(by: z.magnitude)-    return Complex(.log(z.magnitude) + .log(w.lengthSquared)/2, θ)-  }-  -  @inlinable-  public static func log(onePlus z: Complex) -> Complex {-    // Nevin proposed the idea for this implementation on the Swift forums:-    // https://forums.swift.org/t/elementaryfunctions-compliance-for-complex/37903/3+    // The real part of the result is trickier. In exact arithmetic, the+    // real part is just log |z|--many implementations of complex functions+    // simply use this expression as is. However, there are two problems+    // lurking here:+    //+    //   - length can overflow even when log(z) is finite.+    //+    //   - when length is close to 1, catastrophic cancellation is hidden+    //     in this expression. Consider, e.g. z = 1 + δi for small δ.+    //+    //     Because δ ≪ 1, |z| rounds to 1, and so log |z| produces zero.+    //     We can expand using Taylor series to see that the result should+    //     be:+    //+    //         log |z| = log √(1 + δ²)+    //                 = log(1 + δ²)/2+    //                 = δ²/2 + O(δ⁴)+    //+    //     So naively using log |z| results in a total loss of relative+    //     accuracy for this case. Note that this is _not_ constrained near+    //     a single point; it occurs everywhere close to the circle |z| = 1.+    //+    //     Note that this case still _does_ deliver a result with acceptable+    //     relative accuracy in the complex norm, because+    //+    //         Im(log z) ≈ δ ≫ δ²/2 ≈ Re(log z).     //-    // Here's a quick explainer on why it works: in exact arithmetic,+    // There are a number of ways to try to tackle this problem. I'll begin+    // with a simple one that solves the first issue, and _sometimes_ the+    // second, then analyze when it doesn't work for the second case.     //-    //      log(1+z) = (log |1+z|, atan2(y, 1+x))+    // To handle very large arguments without overflow, the standard+    // approach is to _rescale_ the problem. We can do this by finding+    // whichever of x and y has greater magnitude, and dividing through+    // by it. You can think of this as changing coordinates by reflections+    // so that we get a new value w = u + iv with |w| = |z| (and hence+    // Re(log w) = Re(log z), and 0 ≤ u, 0 ≤ v ≤ u.+    let u = max(z.x.magnitude, z.y.magnitude)+    let v = min(z.x.magnitude, z.y.magnitude)+    // Now expand out log |w|:     //-    // where x and y are the real and imaginary parts of z, respectively.+    //     log |w| = log(u² + v²)/2+    //             = log u + log(onePlus: (v/u)²)/2     //-    // The first thing to note is that the expression for the imaginary-    // part works fine as is. If cancellation occurs (because x ≈ -1),-    // then 1+x is exact, and so we have good componentwise relative-    // accuracy. Otherwise, x is bounded away from -1 and 1+x has good-    // relative accuracy, and therefore so does atan2(y, 1+x).+    // This looks promising! It handles overflow well, because log(u) is+    // finite for every finite u, and we have 0 ≤ v/u ≤ 1, so the second+    // term is bounded by 0 ≤ log(1 + (v/u)²)/2 ≤ (log 2)/2. It also+    // handles the example I gave above well: we have u = 1, v = δ, and     //-    // So the real part is the hard part (no surprise, just like expPlusOne).-    // Nevin's clever idea is simply to take advantage of the expansion:+    //     log(1) + log(onePlus: δ²)/2 = 0 + δ²/2     //-    //     Re(log 1+z) = (log 1+z + Conj(log 1+z))/2+    // as expected.     //-    // Log commutes with conjugation, so this becomes:+    // Unfortunately, it does not handle all points close to the unit+    // circle so well; it's easy to see why if we look at the two terms+    // that contribute to the result. Cancellation occurs when the result+    // is close to zero and the terms have opposing signs. By construction,+    // the second term is always positive, so the easiest observation is+    // that cancellation is only a problem for u < 1 (because otherwise+    // log u is also positive, and there can be no cancellation).+    //+    // We are not trying for sub-ulp accuracy, just a good relative error+    // bound, so for our purposes it suffices to have log u dominate the+    // result:+    if u >= 1 || u >= RealType._mulAdd(u,u,v*v) {+      let r = v / u+      return Complex(.log(u) + .log(onePlus: r*r)/2, θ)+    }+    // Here we're in the tricky case; cancellation is likely to occur.+    // Instead of the factorization used above, we will want to evaluate+    // log(onePlus: u² + v² - 1)/2. This all boils down to accurately+    // evaluating u² + v² - 1. To begin, calculate both squared terms+    // as exact head-tail products (u is guaranteed to be well scaled,+    // v may underflow, but if it does it doesn't matter, the u term is+    // all we need).+    let (a,b) = Augmented.twoProdFMA(u, u)+    let (c,d) = Augmented.twoProdFMA(v, v)+    // It would be nice if we could simply use a - 1, but unfortunately+    // we don't have a tight enough bound to guarantee that that expression+    // is exact; a may be as small as 1/4, so we could lose a single bit+    // to rounding if we did that.+    var (s,e) = Augmented.fastTwoSum(-1, a)+    // Now we are ready to assemble the result. If cancellation happens,+    // then |c| > |e| > |b|, |d|, so this assembly order is safe. It's+    // also possible that |c| and |d| are small, but if that happens then+    // there is no significant cancellation, and the exact assembly doesn't+    // matter.+    s = (s + c) + e + b + d+    return Complex(.log(onePlus: s)/2, θ)+  }+  +  @inlinable+  public static func log(onePlus z: Complex) -> Complex {+    // log(1+z) is basically only hard to compute close to the circle+    // |1+z| = 1; inside the circle the real part of the log is negative;+    // outside it positive. On the circle (which passes through 0, -1±i,+    // and -2), it is zero, so we need to be careful about cancellation.+    //+    // To begin, let's just see what domain z actually lies in.+    let r2 = (1 + z).lengthSquared+    // If |1+z| <= ½, we're well inside the circle (and close enough+    // to -1 that 1 + z is computed exactly. If |1+z| => 2, we're far+    // enough outside the circle that we can simply use log(1+z)--1+z+    // may round, but that rounding doesn't cause problematic error in+    // the final result.+    if 2*r2 <= 1 && !(r2 < 2) { return log(1 + z) }+    // Here we're in the annulus ½ < |1+z| < 2, so we need to be careful+    // about cancellation. Instead of computing the real part as+    // log(|1+z|), we use log(onePlus: 2x + |z|²)/2. This is a cute trick+    // that Nevin suggested on the swift forums, which works because:     //     //     Re(log 1+z) = (log 1+z + log 1+z̅)/2-    //                 = log((1+z)(1+z̅)/2+    //                 = log((1+z)(1+z̅))/2     //                 = log(1+z+z̅+zz̅)/2     //-    // This behaves well close to zero, because the z+z̅ term dominates-    // and is computed exactly. Away from zero, cancellation occurs near-    // the circle x(x+2) + y^2 = 1, but everywhere along this curve we-    // have |Im(log 1+z)| >= π/2, so the relative error in the complex-    // norm is well-controlled. We can take advantage of FMA to further-    // reduce the cancellation error and recover a good error bound.-    //-    // The other common implementation choice for log1p is Kahan's trick:+    // https://forums.swift.org/t/elementaryfunctions-compliance-for-complex/37903/3     //-    //     w := 1+z-    //     return z/(w-1) * log(w)+    // So now we just need to compute z+z̅+zz̅ == x(2+x) + y², carefully.+    // 2+x can round; we have -3 < x < 1, so we can compute a head-tail+    // sum easily. It's easiest to see why this is exact with a two-case+    // analysis:     //-    // But this actually doesn't do as well as Nevin's approach does,-    // and requires a complex division, which we want to avoid when we-    // can do so.-    var a = 2*z.x-    // We want to add the larger term first (contra usual guidance for-    // floating-point error optimization), because we're optimizing for-    // the catastrophic cancellation case; when that happens adding the-    // larger term via FMA is always exact. When cancellation doesn't-    // happen, the simple relative error bound carries through the-    // rest of the computation.-    let large = max(z.x.magnitude, z.y.magnitude)-    let small = min(z.x.magnitude, z.y.magnitude)-    a.addProduct(large, large)-    a.addProduct(small, small)-    // If r2 overflowed, then |z| ≫ 1, and so log(1+z) = log(z).-    guard a.isFinite else { return log(z) }-    // Unlike log(z), we do not need to worry about what happens if a-    // underflows.-    return Complex(-      RealType.log(onePlus: a)/2,-      RealType.atan2(y: z.y, x: 1+z.x)-    )+    //  	If x is in [-3, -1], everything is exact because of Sterbenz.+    //    If x is in (-1, +1], 2 + x will generally round, but 2 > |x|,+    //      so no sorting is needed to order the tail computation.+    return log(1+z)`

Yeah, I'm revising it to handle some other cases, but got side-tracked into improving log( ) first.

stephentyrone

comment created time in a month

PullRequestReviewEvent

push eventstephentyrone/swift-numerics

commit sha 0f8ef268d01fe0e77dcb8da46a5aa64590aa9abf

commit sha 3d7462a834b58b10b1af4b1a1e0eeb784835681b

commit sha e07b39453b883f554f1154eedeecd948c11d5888

Swap availability for #if check on Float16.

commit sha 6b24333510e9044cf4716a07bed65eeed6bc6393

Merge pull request #157 from stephentyrone/float16-availability-redux Swap availability for #if check on Float16.

commit sha 23b38ea0e431985ce64b4a9c8befe94cd74cd699

Initial pass over implementing ElementaryFunctions for Complex.

commit sha 446ab158cf84cba001934e3d8c62aa8d5a9135dc

Updates to resync with changes on master. - Renamed scaled/unscaled to multiplied/divided - Adopted new Real module name - Adopted expMinusOne, log(onePlus:) names

commit sha 095570738373c038b3ef4df09908a2ce470f91fc

Rename some tests to reflect what they actually do.

commit sha 23ac35d928d01afc695b0e39d6e70f254404a1c5

WIP on complex elementary functions. Rebased on master, then cleaned up exp and expm1. New algorithm for expm1 that gives good componentwise accuracy as well as normwise. I _think_ that this is fundamentally new; I can't recall seeing it anywhere before, though it's simple enough that I expect someone has thought of it before. Also beefed up testing for exp and expm1. Left sketches of log/pow/sqrt/root in place, though I'm still working on filling out the testing for those.

commit sha f624f4a37b7d83d554973a16c9a0091dbe16464b

Cleanup.

commit sha 9f45eec186bbe04ba58891b7568fd86af3e59e9a

Refactor test support to remove XCTest dependency.

commit sha 5727b7aa4601293322e142fd28d1e80db2727363

Fix typo expMinusOne/exp in near-overflow check.

commit sha daf8db95c053bd040c8ec22c01b204213a86fc19

Moved sampling-based tests out of unit and into an exectuable target.

commit sha b05c2cfe36a1169096e3fe0d56ea511105fa957c

Initial implementation of log(onePlus:)

commit sha 82fb403d0c2bfa5257ce94535548d6d60ff54b78

Fix typo.

commit sha c1081acba5d6744741217bdbb203736c9be5b846

Complex.log with good componentwise relative error Trying to better understand the problems with log1p led me to realize that they exist for log as well in the complex plane. So I reworked log using a novel approach, and now I'm going to circle around to log1p once more.

commit sha e52a603384b0a70e62012c507a2c8267ca121141

Merge branch 'complex-log1p' of github.com:stephentyrone/swift-numerics into complex-log1p

push time in a month

delete branch apple/swift-numerics

delete branch : swift-5.3

delete time in a month

issue closedapple/swift-numerics

I'm running Catalina 10.15.6 (19G2021), yet because of `Float16` I'm unable to compile swift-numerics when targeting macOS.

Swift compiler is reporting the correct version `Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)`, yet I don't understand why it's trying to compile `Float16` when it's explicitly marked as `unavailable` for macOS. Building for an iOS device or the iOS simulator works fine. I tried using `swift-numerics` with a sample Xcode project for a macOS app as well but it fails with the same issues.

Any idea what's up?

swift build

``````» xcrun swift build
extension Float16: Real {
^~~~~~~
public static func cos(_ x: Float16) -> Float16 {
^~~~~~~
public static func cos(_ x: Float16) -> Float16 {
^~~~~~~
public static func sin(_ x: Float16) -> Float16 {
^~~~~~~
public static func sin(_ x: Float16) -> Float16 {
^~~~~~~
public static func tan(_ x: Float16) -> Float16 {
^~~~~~~
public static func tan(_ x: Float16) -> Float16 {
^~~~~~~
public static func acos(_ x: Float16) -> Float16 {
^~~~~~~
public static func acos(_ x: Float16) -> Float16 {
^~~~~~~
public static func asin(_ x: Float16) -> Float16 {
^~~~~~~
public static func asin(_ x: Float16) -> Float16 {
^~~~~~~
public static func atan(_ x: Float16) -> Float16 {
^~~~~~~
public static func atan(_ x: Float16) -> Float16 {
^~~~~~~
public static func cosh(_ x: Float16) -> Float16 {
^~~~~~~
public static func cosh(_ x: Float16) -> Float16 {
^~~~~~~
public static func sinh(_ x: Float16) -> Float16 {
^~~~~~~
public static func sinh(_ x: Float16) -> Float16 {
^~~~~~~
public static func tanh(_ x: Float16) -> Float16 {
^~~~~~~
public static func tanh(_ x: Float16) -> Float16 {
^~~~~~~
public static func acosh(_ x: Float16) -> Float16 {
^~~~~~~
public static func acosh(_ x: Float16) -> Float16 {
^~~~~~~
public static func asinh(_ x: Float16) -> Float16 {
^~~~~~~
public static func asinh(_ x: Float16) -> Float16 {
^~~~~~~
public static func atanh(_ x: Float16) -> Float16 {
^~~~~~~
public static func atanh(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp(_ x: Float16) -> Float16 {
^~~~~~~
public static func expMinusOne(_ x: Float16) -> Float16 {
^~~~~~~
public static func expMinusOne(_ x: Float16) -> Float16 {
^~~~~~~
public static func log(_ x: Float16) -> Float16 {
^~~~~~~
public static func log(_ x: Float16) -> Float16 {
^~~~~~~
public static func log(onePlus x: Float16) -> Float16 {
^~~~~~~
public static func log(onePlus x: Float16) -> Float16 {
^~~~~~~
public static func erf(_ x: Float16) -> Float16 {
^~~~~~~
public static func erf(_ x: Float16) -> Float16 {
^~~~~~~
public static func erfc(_ x: Float16) -> Float16 {
^~~~~~~
public static func erfc(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp2(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp2(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp10(_ x: Float16) -> Float16 {
^~~~~~~
public static func exp10(_ x: Float16) -> Float16 {
^~~~~~~
public static func hypot(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func hypot(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func hypot(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func gamma(_ x: Float16) -> Float16 {
^~~~~~~
public static func gamma(_ x: Float16) -> Float16 {
^~~~~~~
public static func log2(_ x: Float16) -> Float16 {
^~~~~~~
public static func log2(_ x: Float16) -> Float16 {
^~~~~~~
public static func log10(_ x: Float16) -> Float16 {
^~~~~~~
public static func log10(_ x: Float16) -> Float16 {
^~~~~~~
public static func pow(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func pow(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func pow(_ x: Float16, _ y: Float16) -> Float16 {
^~~~~~~
public static func pow(_ x: Float16, _ n: Int) -> Float16 {
^~~~~~~
public static func pow(_ x: Float16, _ n: Int) -> Float16 {
^~~~~~~
public static func root(_ x: Float16, _ n: Int) -> Float16 {
^~~~~~~
public static func root(_ x: Float16, _ n: Int) -> Float16 {
^~~~~~~
public static func atan2(y: Float16, x: Float16) -> Float16 {
^~~~~~~
public static func atan2(y: Float16, x: Float16) -> Float16 {
^~~~~~~
public static func atan2(y: Float16, x: Float16) -> Float16 {
^~~~~~~
public static func logGamma(_ x: Float16) -> Float16 {
^~~~~~~
public static func logGamma(_ x: Float16) -> Float16 {
^~~~~~~
[1/1] Compiling RealModule Float16+Real.swift
``````

Xcode

``````CompileSwift normal x86_64 /Users/Adam/Library/Developer/Xcode/DerivedData/aaaaaaaaaaaaaa-aofjcusrwcvzregrwblywxeuucga/SourcePackages/checkouts/swift-numerics/Sources/RealModule/Float16+Real.swift (in target 'RealModule' from project 'swift-numerics')

/Users/Adam/Library/Developer/Xcode/DerivedData/aaaaaaaaaaaaaa-aofjcusrwcvzregrwblywxeuucga/SourcePackages/checkouts/swift-numerics/Sources/RealModule/Float16+Real.swift:18:11: error: cannot find type 'Float16' in scope
extension Float16: Real {
^~~~~~~
``````

closed time in a month

b3ll

issue commentapple/swift-numerics

@b3ll I just tagged 0.0.8, which should resolve this.

b3ll

comment created time in a month

created tagapple/swift-numerics

Numerical APIs for Swift

created time in a month

release apple/swift-numerics

released time in a month

push eventapple/swift-numerics

commit sha e07b39453b883f554f1154eedeecd948c11d5888

Swap availability for #if check on Float16.

commit sha 6b24333510e9044cf4716a07bed65eeed6bc6393

Merge pull request #157 from stephentyrone/float16-availability-redux Swap availability for #if check on Float16.

push time in a month

PR merged apple/swift-numerics

Xcode 12 on Catalina has Swift 5.3 but the macOS 10.15 SDK, which breaks the conditionals around Float16. Since the type is unconditionally unavailable on macOS, we can simply `#if`-out any Float16 extensions instead.

+8 -13

1 comment

5 changed files

stephentyrone

pr closed time in a month

pull request commentapple/swift-numerics

@lorentey yeah, step 2 here is definitely to figure out how to avoid this mess in the future =)

stephentyrone

comment created time in a month

PR opened apple/swift-numerics

Xcode 12 on Catalina has Swift 5.3 but the macOS 10.15 SDK, which breaks the conditionals around Float16. Since the type is unconditionally unavailable on macOS, we can simply `#if`-out any Float16 extensions instead.

+8 -13

0 comment

5 changed files

pr created time in a month

create barnchstephentyrone/swift-numerics

created branch time in a month

issue commentapple/swift-numerics

Hi @b3ll, I was able to reproduce this. The basic issue is that 12A7209 contains a macOS 10.15 SDK (which correctly doesn't have Float16), rather than a macOS 10.16 SDK. By contrast it has an iOS 14 SDK, so there's no issue for iOS platforms. I'll have to think a little bit about what the best way to handle this in Swift-Numerics is, but I'll try to get a fix up later today. In the very-short term, you can pin to 0.0.6 as a workaround.

b3ll

comment created time in a month

pull request commentapple/swift-numerics

Note that this doesn't yet handle Im(z) ≈ -1 right. That's relatively easy to fix up, but I have some other work to do today, so it'll have to wait.

stephentyrone

comment created time in a month

PullRequestReviewEvent

push eventstephentyrone/swift-numerics

commit sha a5a6a941c4ae9effbb68f75a8a3db026a4fdf752

Fix typo.

push time in a month

pull request commentapple/swift-numerics

@NevinBR what would you prefer to be credited as in the comments?

stephentyrone

comment created time in a month

PR opened apple/swift-numerics

Adapts @NevinBR's cute factorization of the real part here:

https://forums.swift.org/t/elementaryfunctions-compliance-for-complex/37903/3

Adds a little bit of FMA magic to avoid catastrophic cancellation and recover a good componentwise error bound as well as an error bound in the complex norm.

+62 -1

0 comment

1 changed file

pr created time in a month

create barnchstephentyrone/swift-numerics

created branch time in a month

push eventstephentyrone/swift-numerics

commit sha 0f02c6585465356cf5ba4d0937221dcc81fd7690

Initial implementation of log(onePlus:)

push time in a month

Pull request review commentapple/swift

` public struct \${Self}     // undefined if it overflows. %     if not (FloatBits == 16 and bits >= 32): # Float16 is always in-range for 32- and 64-bit ints.     guard source > \${str(lower)}.0 && source < \${str(upper)}.0 else {+%     else:`

Alternatively, I think we could probably simply eliminate the gyb conditional and use `source.isFinite && existing condition`, right?

xwu

comment created time in a month

PullRequestReviewEvent

Pull request review commentapple/swift

` public struct \${Self}     // undefined if it overflows. %     if not (FloatBits == 16 and bits >= 32): # Float16 is always in-range for 32- and 64-bit ints.     guard source > \${str(lower)}.0 && source < \${str(upper)}.0 else {+%     else:`

We also need to handle UInt16, I think (max value 65536, which rounds to infinity in Float16).

xwu

comment created time in a month

PullRequestReviewEvent

delete branch stephentyrone/swift

delete branch : random-simplification

delete time in a month

PR closed apple/swift

Reviewers

We don't actually need a divide or two paths or any of the other complexity here; the optimizer knows how to do this for us.

+8 -12

2 changed files

stephentyrone

pr closed time in a month

Pull request review commentapple/swift

` extension BinaryFloatingPoint {   /// - Parameter value: A floating-point value to be converted.   @inlinable   public init?<Source: BinaryFloatingPoint>(exactly value: Source) {-    let (value_, exact) = Self._convert(from: value)-    guard exact else { return nil }-    self = value_+    // We define exactness by equality after roundtripping; since NaN is never+    // equal to itself, it can never be converted exactly.+    if value.isNaN { return nil }+    `

I think we can simplify the logic downstream by pulling out zero and infinity here (since both are always exact). WDYT?

xwu

comment created time in a month

PullRequestReviewEvent

pull request commentapple/swift-se-0282-experimental

SGTM.

lorentey

comment created time in a month

PullRequestReviewEvent

PR closed apple/swift

Initial implementation of isAlmostEqual(to:) and isAlmostZero() swift-evolution-pending-discussion

Pending Swift-evolution review.

+221 -0

2 changed files

stephentyrone

pr closed time in a month

push eventapple/swift

[stdlib] Silence signaling NaN in generic conversions (#33902)

push time in a month

delete branch apple/swift

delete branch : silence-of-the-nans

delete time in a month

PR merged apple/swift

This PR is a follow-up to #33826, using a cheap operation (multiplying by one) to silence signaling NaN in generic conversions.

(Mostly I'm proud of the branch name.)

<!-- Before merging this pull request, you must run the Swift continuous integration tests. For information about triggering CI builds via @swift-ci, see: https://github.com/apple/swift/blob/master/docs/ContinuousIntegration.md#swift-ci

Thank you for your contribution to Swift! -->

+4 -2

1 comment

1 changed file

xwu

pr closed time in a month

PullRequestReviewEvent
PullRequestReviewEvent

pull request commentapple/swift

The new concrete implementations will have to be marked `@_alwaysEmitIntoClient`.

xwu

comment created time in a month

push eventstephentyrone/swift-numerics

commit sha a76404b263a8ddba34a9f3e91f7271c0d2c13777

WIP

push time in a month

pull request commentapple/swift

I'm fine with the diagnostics generated. Pavel or Holly may have feedback on the implementation details, so please let one of them review as well.

theblixguy

comment created time in 2 months

Pull request review commentapple/swift

` static void diagnoseExplicitUseOfLazyVariableStorage(const Expr *E,   const_cast<Expr *>(E)->walk(Walker); } +static void diagnoseComparisonWithNaN(const Expr *E, const DeclContext *DC) {+  class ComparisonWithNaNFinder : public ASTWalker {+    const ASTContext &C;+    const DeclContext *DC;++  public:+    ComparisonWithNaNFinder(const DeclContext *dc)+        : C(dc->getASTContext()), DC(dc) {}++    void tryDiagnoseComparisonWithNaN(BinaryExpr *BE) {+      ValueDecl *comparisonDecl = nullptr;++      // The == and != methods take two arguments.+      if (BE->getArg()->getNumElements() != 2) {+        return;+      }++      // Dig out the function the arguments are being passed to.+      if (auto Fn = BE->getFn()) {+        if (auto DSCE = dyn_cast<DotSyntaxCallExpr>(Fn)) {+          comparisonDecl = DSCE->getCalledValue();+        } else {+          comparisonDecl = BE->getCalledValue();+        }+      }++      // Bail out if it isn't a function.+      if (!comparisonDecl || !isa<FuncDecl>(comparisonDecl)) {+        return;+      }++      // We're only interested in == and != functions.+      auto comparisonDeclName = comparisonDecl->getBaseIdentifier();+      if (!(comparisonDeclName.is("==") || comparisonDeclName.is("!=") ||+            comparisonDeclName.is("<=") || comparisonDeclName.is("<") ||+            comparisonDeclName.is(">") || comparisonDeclName.is(">="))) {+        return;+      }++      auto firstArg = BE->getArg()->getElement(0);+      auto secondArg = BE->getArg()->getElement(1);++      // Both arguments must conform to FloatingPoint protocol.+      auto conformsToFpProto = [&](Type type) {+        auto fpProto = C.getProtocol(KnownProtocolKind::FloatingPoint);+        return !TypeChecker::conformsToProtocol(type, fpProto,+                                                const_cast<DeclContext *>(DC))+                    .isInvalid();+      };++      if (!conformsToFpProto(firstArg->getType()) ||+          !conformsToFpProto(firstArg->getType())) {+        return;+      }++      // Dig out the declarations for the arguments.+      ValueDecl *firstVal = nullptr;+      ValueDecl *secondVal = nullptr;+      if (auto DRE = dyn_cast<DeclRefExpr>(firstArg)) {+        firstVal = DRE->getDecl();+      } else if (auto MRE = dyn_cast<MemberRefExpr>(firstArg)) {+        firstVal = MRE->getMember().getDecl();+      }++      if (auto DRE = dyn_cast<DeclRefExpr>(secondArg)) {+        secondVal = DRE->getDecl();+      } else if (auto MRE = dyn_cast<MemberRefExpr>(secondArg)) {+        secondVal = MRE->getMember().getDecl();+      }++      // One of them has to be '.nan', so if we don't have declarations+      // for both, then bail out.`

Extremely minor nit: should be "if we don't have declarations for either, then bail out," I think.

theblixguy

comment created time in 2 months

PullRequestReviewEvent
more