# Quadratic equation solver in Ruby

I wrote a simple quadratic equation solver with Ruby to help me solve quadratic equations. Please tell me what I did right or wrong, and what I can do to improve it.

#!/usr/bin/ruby

if ARGV.length != 3
STDERR << "Usage: #{$0} <a> <b> <c>\n" exit 1 end a = ARGV[0].to_i b = ARGV[1].to_i c = ARGV[2].to_i disc = b**2 - 4*a*c d_str = (disc >= 0) ? (Math.sqrt(disc) % 1 == 0) ? Math.sqrt(disc).to_i : "√#{disc}" : "√#{disc}" s = "#{-b} ± #{d_str}" d = "#{2*a}" puts "\e[4m#{s}\e[0m" #puts "─"*(s.length) puts d.center s.length  • I would show the calculated value Math.sqrt(disc) even when it is a floating point number. Its also a good idea to check before using terminal control codes (A simple, though not foolproof, way is to check $stdout.isatty). The colorize gem is also, though ti does add a dependency to your code. Mar 2 '17 at 17:29
• Any updates on this? Mar 4 '17 at 7:54

What you did right is solve the problem.

What you did "wrong" really depends on a number of factors, so I'll respond with what you can do to improve it, with an example that implements those improvements. Note, many improvements will make the code longer.

# Improvements

• Use the shebang #!/usr/bin/env ruby. It allows the environment to have ruby in a different directory than /usr/bin/ruby.
• Use $stderr.puts "Usage: #{$0} <a> <b> <c>". $stderr is a global variable and can be changed. It allows standard error to be redirected, say to a file or a logger. It would be restored by $stderr = STDERR.
• Ruby indentation standard is 2 spaces and no hard tabs.
• Use disc_str to indicate it is a String version of disc.
• Use disc_sqrt_str to indicate it is a String version of disc_sqrt.
• Use a variable to reduce wasted calculations; sqrt is not a simple calculation.
• Nested ? operators can be hard to read and can lead to mistakes.
• Use numerator and denominator to convey intention, instead of s and d.
• Allow for the case when discriminant is zero.
• Use parentheses for arguments when method calls are arguments. Thus instead of f g h x do f g(h(x)) or even better f(g(h(x))).
• Use discriminant because it is a Mathematics domain term. A non-mathematics literate developer will be blocked from maintaining it because Googling "disc" won't help them.
• Eliminate common factors.
• Eliminate fraction if denominator is 1.
• Define methods for greater flexibility.
• Include some tests to test each pathway.
• Use appropriate comments and self-commenting code by using good names.
• Use Rubocop to provide similar automated improvements.
• Use the colorize gem in an optional way, since it is not actually necessary.
• Check if it is available by using ''.respond_to?(:underline), which will mean that it is more flexible because another method could be added to String which might do a similar thing or even something rather different; CSS, LaTeX, ASCII, ... .
• Use Unicode codes instead of the characters directly. Some editors may not display Unicode characters properly and could change the characters to something unexpected.
#!/usr/bin/env ruby
#
#
#     Luis Esteban    9 August 2020
#       review of code of Dmitry Kudriavtsev

# Output rational solutions to the quadratic equation
#   For a.x^2 + b.x + c = 0
#
#   x = (-b ± √d) / 2a
#   d = b^2 - 4.a.c       (discriminant)
#

begin
require 'colorize'
end

ALLOW_IMAGINARY = true

discriminant = b**2 - 4*a*c
denominator  = 2 * a

if discriminant > 0
discriminant_sqrt = Math.sqrt(discriminant)
if discriminant_sqrt % 1 == 0
b, discriminant, denominator = simplify(b, discriminant_sqrt.to_i, denominator)
numerator = [-b, " \u00b1 ", discriminant]
else
b, discriminant, denominator = simplify_with_sqrt(b, discriminant, denominator)
numerator = [-b, " \u00b1 \u221a", discriminant]
end
elsif discriminant == 0
solution    = Rational(-b, denominator)
numerator   = [solution.numerator]
denominator = solution.denominator
else
if ALLOW_IMAGINARY
discriminant_sqrt = Math.sqrt(-discriminant)
discriminant_sqrt = discriminant_sqrt.to_i if discriminant_sqrt % 1 == 0
if discriminant_sqrt % 1 == 0
b, discriminant, denominator = simplify(b, discriminant_sqrt.to_i, denominator)
numerator = [-b, " \u00b1 i \u2a2f ", discriminant]
else
b, discriminant, denominator = simplify_with_sqrt(b, discriminant, denominator)
numerator = [-b, " \u00b1 i \u2a2f \u221a", -discriminant]
end
else
numerator = []
end
end

[numerator, denominator]
end

def simplify(b, discriminant, denominator)
gcd = [b, discriminant, denominator].inject(&:gcd)
gcd = -gcd unless denominator.positive?

[b, discriminant, denominator].map{|n| n / gcd }
end

def simplify_with_sqrt(b, discriminant, denominator)
gcd = [b**2, discriminant, denominator**2].inject(&:gcd)
gcd_sqrt = Math.sqrt(gcd).round
if denominator.negative?
gcd      = -gcd
gcd_sqrt = -gcd_sqrt
end

[
b            / gcd_sqrt,
discriminant / gcd,
denominator  / gcd_sqrt
]
end

def display_fraction(numerator, denominator)
size        = numerator.size
numerator   = numerator.join
denominator = denominator.to_s
width       = [numerator, denominator].map(&:length).max + 2

if size > 0
if denominator != "1"
if ''.respond_to?(:underline)
puts "#{numerator}".center(width).underline
puts denominator.center(width)
else
puts numerator.center(width)
puts "─" * width
puts denominator.center(width)
end
else
puts numerator
end
else
puts "No solutions"
end
end

if ARGV.length != 3
$stderr.puts "Usage: #{$0} <a> <b> <c>"
exit 1
else
a, b, c = ARGV.map(&:to_i)
puts "solution(s) to #{a}x\u00b2 + #{b}x + #{c} = 0"

end

# Testing
#  [
#    [1, 1, -12],    # Integer discriminant
#    [-1, 1, 12],    # Make denominator positive
#    [1, 2, 0],      # Don't show denominator if 1
#    [4, 4, 1],      # Don't show discriminant if 0
#    [1, 2, -17],    # Don't show denominator if 1 with surd
#    [2, 4, 2],      # Don't show denominator if 1 and  discriminant if 0
#    [1, 1, -13],    # Show surd
#    [1, 2, 17],     # Imaginary solution with integer discriminant no denominator
#    [1, 2, 16],     # Imaginary solution with correct cancellation of surd
#    [2, 0, 2],      # Imaginary solution with surd, hide denominator
#    [2, 8, 16],     # Imaginary solution with integer discriminant
#  ].each do |a,b,c|
#    puts "For: a = #{a.inspect}, b = #{b.inspect}, c = #{c.inspect}"
#    puts