홈

현이의 개발 이야기

자바로 분수 나타내기

Java 2024. 06. 09. 03:14

프로그래머스 문제 중 Lv 2짜리 아날로그 시계라는 문제를 봤다.
주어진 시간 범위 내에 초침이 시침, 분침과 겹치는 횟수를 세는 문제인데,
이 횟수를 세는 것은 그렇게 어렵지 않았지만 난관은 시침, 분침, 초침이 모두 겹칠 때에는 한 번으로 세어야 한다는 점이었다.
이 부분에서 분수를 나타낼 필요성을 느껴 분수를 다루는 클래스를 작성해 보았다.

생성자

가장 먼저, 분수에 있어서 필요한 분자와 분모를 정수형으로 선언해 주었다.
값을 나타내는 용도의 클래스인 만큼, final을 붙여 불변성을 부여하였다.
final로 선언된 원시 자료형이기 때문에, 이 두 값은 클래스 내부에서 관리함에도 불구하고 public으로 선언될 수 있다.
외부에서 자유롭게 참조는 가능하지만, final이 붙었기 때문에 값을 변경할 수 없기 때문이다.
public class Fraction {
  public final long numerator;
  public final long denominator;
}
가장 베이스가 되는 생성자로는 이 두 값을 입력받는 생성자를 정의하였다.
이때, 최대 공약수로 numerator와 denomiator를 나누어 주어 약분까지 해주었다.
public Fraction(long numerator, long denominator) {
  long gcd = gcd(Math.abs(numerator), Math.abs(denominator));
  this.numerator = numerator / gcd;
  this.denominator = denominator / gcd;
}

private static long gcd(long a, long b) {
  if (b == 0) return a;
  return gcd(b, a % b);
}
그리고 조금 더 편하게 사용할 수 있도록 디폴트 생성자와 정수를 입력받는 생성자를 오버로딩 하였다.
public Fraction() {
  this(0);
}

public Fraction(long value) {
  this(value, 1);
}

덧셈, 뺄셈, 곱셈

덧셈, 뺄셈, 곱셈은 각 연산의 정의대로 구현하였다.
이 때, 새로운 Fraction 객체를 생성하여 반환함으로써 불변성을 유지하였다.
또, 생성자에서 약분을 하므로, 모든 Fraction 객체는 기약 분수 상태를 항상 확보하게 된다.
public Fraction add(Fraction other) {
  long numerator = this.numerator * other.denominator + this.denominator * other.numerator;
  long denominator = this.denominator * other.denominator;
  return new Fraction(numerator, denominator);
}

public Fraction subtract(Fraction other) {
  long numerator = this.numerator * other.denominator - this.denominator * other.numerator;
  long denominator = this.denominator * other.denominator;
  return new Fraction(numerator, denominator);
}

public Fraction multiply(Fraction other) {
  long numerator = this.numerator * other.numerator;
  long denominator = this.denominator * other.denominator;
  return new Fraction(numerator, denominator);

역수, 나눗셈

나눗셈의 경우 그대로 구현할 수 도 있지만, 역수를 구한 후 곱셈으로 처리하는 것이 구현 상 간단하다.
다만, 분수가 0인 경우 역수를 구할 수 없으므로 이런 경우에는 Exception을 발생시켜 준다.
수학 연산 중 발생한 Exception이니 ArithmeticException을 발생시킨다.
public Fraction inverse() {
  if (numerator == 0) {
    throw new ArithmeticException("Cannot inverse zero.");
  }
  return new Fraction(denominator, numerator);
}
나눗셈은 이를 이용하여 역수를 구해, 곱해주기만 하면 된다.
public Fraction divide(Fraction other) {
  return multiply(other.inverse());
}

근사치

분수가 나타내는 근삿값을 알기 위한 메서드도 추가해 준다.
public double value() {
  return (double) numerator / denominator;
}

Object 메서드

값을 나타내는 클래스이니, 동등 비교와 해시 등의 메서드 오버라이딩을 해주어야 한다.
이를 해주지 않으면, Collection 컨테이너들을 사용할 때 해당 컨테이너들이 제 힘을 발휘하지 못하게 됨은 물론, 두 분수를 비교할 때나 그 값을 확인할 때에도 불편해진다.
@Override
public String toString() {
  return String.format("%d / %d", numerator, denominator);
}

@Override
public boolean equals(Object object) {
  if (this == object) return true;
  if (object == null || getClass() != object.getClass()) return false;
  Fraction other = (Fraction) object;
  return numerator == other.numerator && denominator == other.denominator;
}

@Override
public int hashCode() {
  return Objects.hash(numerator, denominator);
}

전체 코드

import java.util.Objects;

public class Fraction implements Comparable<Fraction> {
  public final long numerator;
  public final long denominator;

  public Fraction() {
    this(0);
  }

  public Fraction(int value) {
    this(value, 1);
  }

  public Fraction(long numerator, long denominator) {
    long gcd = gcd(Math.abs(numerator), Math.abs(denominator));
    this.numerator = numerator / gcd;
    this.denominator = denominator / gcd;
  }

  public Fraction add(int value) {
    return new Fraction(numerator + value * denominator, denominator);
  }

  public Fraction add(Fraction other) {
    long numerator = this.numerator * other.denominator + this.denominator * other.numerator;
    long denominator = this.denominator * other.denominator;
    return new Fraction(numerator, denominator);
  }

  public Fraction subtract(Fraction other) {
    long numerator = this.numerator * other.denominator - this.denominator * other.numerator;
    long denominator = this.denominator * other.denominator;
    return new Fraction(numerator, denominator);
  }

  public Fraction multiply(int multiplier) {
    return new Fraction(numerator * multiplier, denominator);
  }

  public Fraction multiply(Fraction other) {
    long numerator = this.numerator * other.numerator;
    long denominator = this.denominator * other.denominator;
    return new Fraction(numerator, denominator);
  }

  public Fraction divide(Fraction other) {
    return multiply(other.inverse());
  }

  public Fraction inverse() {
    if (numerator == 0) {
      throw new ArithmeticException("Cannot inverse zero.");
    }
    return new Fraction(denominator, numerator);
  }

  public Fraction mod(int value) {
    return new Fraction(numerator % (value * denominator), denominator);
  }

  public double value() {
    return (double) numerator / denominator;
  }

  @Override
  public String toString() {
    return String.format("%d / %d", numerator, denominator);
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) return true;
    if (object == null || getClass() != object.getClass()) return false;
    Fraction other = (Fraction) object;
    return numerator == other.numerator && denominator == other.denominator;
  }

  @Override
  public int hashCode() {
    return Objects.hash(numerator, denominator);
  }

  @Override
  public int compareTo(Fraction f) {
    if (equals(f)) {
      return 0;
    } else if (value() < f.value()) {
      return -1;
    } else {
      return 1;
    }
  }

  private static long gcd(long a, long b) {
    if (b == 0) return a;
    return gcd(b, a % b);
  }
}
댓글 0

로그인이 필요합니다.
로그인