Handling divide by zero

I have some SwiftUI code where I’m setting a line length with a value:

size.height/cos(angle)

Naturally, cos(angle) == 0 if the angle in 90 or 270.

(Did you know that a div by 0 bug in your drawing code will freeze the macOS window server? I didn’t.)

Anyway, my current solution is this function:

func cosNZ(_ input: Double) -> Double {
  let x = cos(input)
  if x == 0.0 { return Double.leastNonzeroMagnitude }
  return x
}

Is there a better practice? I’m using this as a reasonable approximation, and letting the graphic code handle my absurd coordinate.

The value can be != 0, but still small enough to cause overflow result in infinity. And I would not compare floating point values using == in general. I even think that maybe Double (and other floating point types) should conform to Comparable, but should not to Equatable.

1 Like

It depends on what you're trying to do. If it's just that your graphics library handles very large finite values but not infinity, then I might do fmin(size.height/cos(angle), SomeVeryLargeFiniteValue) to cap the result (with a corresponding fmax(-SomeVeryLargeFiniteValue) if the value might also be negative).

2 Likes

Division by zero in floating-point is not an error. It simply results in infinity (when dividing a non-zero value by zero) or nan (in the case of 0/0), which are perfectly well-formed floating-point values.

Furthermore, there is no floating-point number x for which cos(x) is zero (the smallest that cos(x) can get in Double is about 1e-19; I would have to look up the exact value). Your x == 0.0 case will never be used.


edit: I checked my notes (KC Ng’s extremely readable note “ARGUMENT REDUCTION FOR HUGE ARGUMENTS: Good to the Last Bit”), the smallest magnitude cosine for Double occurs when

x = ±5319372648326541416707296656673541083813475031793921822105998164685326343987747477646239125204069843392466931105720371047561653378447496736288905533500277726150903890962697774418679535123008556835980236851047840822029788166318932319835828816270258618761216.0

and cos(x) ≅ -4.6871E-19.

12 Likes

I’m a little amazed that fmin(4/0, 0.0000000000001) doesn’t error out, wherevar x = 4/0 does.

1 Like

The context from fmin causes the type of 4/0 to be inferred as Double; 4/0 computed as Double is .infinity.

2 Likes

Now I’m a little afraid to test it, having gone through about 30 or 40 Window server crashes before it occured to me that it might be div by zero.

Do you have a minimal reproducible sample?

It's possible for CoreGraphics to crash on big numbers, maybe that's what causing it, or SwiftUI is the same way.

2 Likes

1.0 / Double.leastNonzeroMagnitude results in infinity, the same way as 1.0 / 0.0 (tested on Windows).

Trying to produce one, but now compilation is crashing.

Here’s the code that is freaking it out:

import SwiftUI

struct TestView: View {
  @State var angle: Angle = Angle(radians: Double.pi/1.5)
  let size = CGSize(width: 800, height: 600)
  var body: some View {
    Path { path in
      path.move(to: CGPoint(x: size.width/cos(angle.radians) , y: size.height/cos(angle.radians)))
      path.addLine(to: CGPoint(x: 100, y: 100))
    }.stroke(Color.red)
  }
}

#Preview {
  TestView()
}

Filed: FB21779423

Hmm, so I guess what I need to do is choose something less large, so like

let x = height/cos(angle)
return x.isInfinite ? Double.max : x

Heh, it occurs to me that I could just choose a number bigger than most screens and be fine. I just struggle with math in the math world, and math in the digital world.

Thank you.

Interestingly changing from cos to Darwin.cos fixes compilation.

1 Like

Why yes, yes it does. How odd.

Added that to the FB entry.

I suspect that you calculate the coordinates incorrectly. When you get those big numbers for x or y, is this really the point where you want to move your path?

My program is a math toy for visualizing the relationship between the trigonometry functions. In this particular case, I’m trying to demonstrate the nature of tangent lines, which approach infinite length near the 90s.

I don’t mind fudging them a little, but I’m hoping for math which works well in a variety of viewing situations, such as 3D ones. If I have to hang on some math safety checks, so be it, but I’m trying to keep it very clean.

1 Like

Please file a separate bug report for this using Feedback Assistant. An app should never be able to take down WindowServer.

9 Likes

To continue the oddity, this compiles:

It also runs, despite the fact that cos(angle.radians) evaluates to zero (at least as far as Text is concerned).

And here I thought computers were supposed to be deterministic… :stuck_out_tongue:

import SwiftUI



struct TestView: View {
  @State var angle: Angle = Angle(radians: Double.pi/2.0)
  let size = CGSize(width: 800, height: 600)
  var body: some View {
    Text("\(size.width/cos(angle.radians))   \(size.height/Darwin.sin(angle.radians))")

    Path { path in
      path.move(to: CGPoint(x: size.width/cos(angle.radians),
                            y: size.height/Darwin.sin(angle.radians)))
      path.addLine(to: CGPoint(x: 10, y: 10))
    }.stroke(Color.red)
  }
}



#Preview {
  TestView()
}

Working on this. macOS runs okay in a VM, which is nice. Crashes take 2-3 minutes to recover, which is less good. Especially since I seem to need a lot more code to set up the right conditions.

Worth the work, though.