From 51ef733b6d34b60eeb58fbf3c4de4c7980398c9f Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 00:38:27 +0900 Subject: [PATCH 01/10] feat(Lotto): add condition --- src/Lotto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e..416a4bee9 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -7,8 +7,8 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 6) { - throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); + if (numbers.length !== 6 || !numbers.every(num => num >= 1 && num <= 45)) { + throw new Error("[ERROR] 로또 번호는 1 ~ 45 사이의 숫자 6개여야 합니다."); } } From 87e9b7cb2b8c29519d79c02154a290cfe0040b31 Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 00:58:31 +0900 Subject: [PATCH 02/10] feat: add LottoMachine --- src/LottoMachine.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/LottoMachine.js diff --git a/src/LottoMachine.js b/src/LottoMachine.js new file mode 100644 index 000000000..e65f1fec2 --- /dev/null +++ b/src/LottoMachine.js @@ -0,0 +1,12 @@ +class LottoMachine { + generateLotto() { + const numbers = MissionUtils.Random.pickUniqueNumbersinRange(1, 45, 6); + return new Lotto(numbers); + } + + generateWinningNumbers(){ + const winningNumbers = MissionUtils.Random.pickUniqueNumbersinRange(1,45,6); + const bonusNumber = MissionUtils.Random.pickUniqueNumbersinRange(1,45,1)[0]; + return {winningNumbers, bonusNumber}; + } + } \ No newline at end of file From a037c7d4d68db332fad9f1b95178753c3d10feaf Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:18:23 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat(Lotto):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=ED=8B=B0=EC=BC=93=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Lotto.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index 416a4bee9..183655783 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,9 +1,10 @@ +// 로또 티켓을 생성하고 번호를 관리하는 클래스 class Lotto { - #numbers; + #numbers; // 프라이빗 필드로 선언 constructor(numbers) { - this.#validate(numbers); - this.#numbers = numbers; + this.#validate(numbers); // 유효성 검사 + this.#numbers = numbers; //인스턴스 변수에 저장 } #validate(numbers) { @@ -11,7 +12,11 @@ class Lotto { throw new Error("[ERROR] 로또 번호는 1 ~ 45 사이의 숫자 6개여야 합니다."); } } - + + // 프라이빗 필드에 접근할 수 있는 안전한 인터페이스를 제공 + getNumbers(){ + return this.#numbers; // #numbers 를 외부에 반환하여, 클래스 외부에서 로또 번호 목록을 읽을 수 있도록 한다. + } // TODO: 추가 기능 구현 } From bb3e6343052f32d7953329b5c739a984d9089bfe Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:19:04 +0900 Subject: [PATCH 04/10] =?UTF-8?q?fix(App):=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a5d..08f27dc3a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,12 @@ +import LottoGame from "./LottoGame.js"; +// 프로그램의 진입점이자 전체 흐름을 관리하는 역할 +// 비동기 작업을 수행하기 위한 메서드 +// run() 메서드는 프로그램의 메인 진입점. 이 메서드에서 로또 게임의 각 단계를 비동기로 처리한다. class App { - async run() {} + async run() { + const game = new LottoGame(); + await game.start(); + } } export default App; From 914669039de2065df515af3887a65f0f7d62eb18 Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:19:35 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat(Constants):=20=EC=83=81=EA=B8=88=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B0=80=EA=B2=A9=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Constants.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/Constants.js diff --git a/src/Constants.js b/src/Constants.js new file mode 100644 index 000000000..9b90efef1 --- /dev/null +++ b/src/Constants.js @@ -0,0 +1,9 @@ +// 상금, 로또 가격 등의 상수를 관리한다. +export const LOTTO_PRICE = 1000; +export const PRIZE_TABLE = { + 1: 2000000000, + 2: 30000000, + 3: 1500000, + 4: 50000, + 5: 5000, +} \ No newline at end of file From dbb22af724e2e50beeeacb5f5c648d6ff8d22c87 Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:20:37 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat(LottoMachine):=20=EB=8B=B9=EC=B2=A8?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LottoMachine.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LottoMachine.js b/src/LottoMachine.js index e65f1fec2..f66dac43f 100644 --- a/src/LottoMachine.js +++ b/src/LottoMachine.js @@ -1,3 +1,4 @@ +// 로또 티켓과 당첨 번호를 생성하는 역할 class LottoMachine { generateLotto() { const numbers = MissionUtils.Random.pickUniqueNumbersinRange(1, 45, 6); From 1200cd693c16b929e8b1ec25feac231c8bab00df Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:21:32 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat(LottoGame):=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=9D=90=EB=A6=84=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LottoGame.js | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/LottoGame.js diff --git a/src/LottoGame.js b/src/LottoGame.js new file mode 100644 index 000000000..058df8957 --- /dev/null +++ b/src/LottoGame.js @@ -0,0 +1,115 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; +import LottoResult from "./LottoResult.js"; +import LottoMachine from "./LottoMachine.js" +import { LOTTO_PRICE } from "./Constants.js"; + +// 로또 게임의 메인 흐름을 관리 +class LottoGame { + // 로또 게임의 초기화 작업 + constructor(){ + this.lottoMachine = new LottoMachine(); //로또 번호 생성기(인스턴스 생성) + this.lottoResult = new LottoResult(); //결과 계산기 + } + // 비동기 작업을 수행하기 위한 메서드 + async start(){ + try + { + const amount = await this.inputPurchaseAmount(); //구입 금액 입력 및 검증 + + // 로또 발행 + const purchasedLottos = this.purchaseLottos(amount); + this.printPurchasedLottos(purchasedLottos); + + // 당첨 번호 및 보너스 번호 입력 + const { winningNumbers, bonusNumber } = await this.inputWinningNumbers(); + + // 당첨 결과 계산 + this.lottoResult.calculateRank(purchasedLottos, winningNumbers, bonusNumber); + + // 당첨 결과 및 통계 출력 + this.printLottoStatistics(this.lottoResult.getStatistics(), amount); + + } catch (error) + { + MissionUtils.Console.print(error.message); + } + } + //구입 금액 입려받고 유효성 검사. + async inputPurchaseAmount(){ + const amountStr = await MissionUtils.Console.readLineAsync("구입 금액을 입력해 주세요: "); + const amount = parseInt(amountStr, 10); //문자열을 정수로 변환 + + if(isNaN(amount) || amount % LOTTO_PRICE !== 0) { + throw new Error("[ERROR] 로또 구입 금액은 1000원 단위여야 합니다."); + } + return amount; + } + + purchaseLottos(amount){ //구매한 로또 배열 반환 + const lottoCount = amount / LOTTO_PRICE; + const lottos = []; // 스택에 저장 + + for(let i = 0; i < lottoCount; i++){ + const numbers = this.lottoMachine.generateLotto(); // 로또 번호 생성 + const lotto = new Lotto(numbers); //Lotto 인스턴스 생성(유효성 검사는 Lotto 클래스가 수행) + lottos.push(lotto); + } + + return lottos; + } + + //당첨 번호 입력받기 + async inputWinningNumbers() { + //사용자 입력 변수 선언 + const WinningNumbersStr = await MissionUtils.Console.readLineAsync("당첨 번호를 입력해 주세요. "); + const bonusNumberStr = await MissionUtils.Console.readLineAsync("보너스 번호를 입력해 주세요. "); + + const winningNumbers = WinningNumbersStr.split(",").map(Number); // , 단위로 나눈 뒤 매핑 + const bonusNumber = parseInt(bonusNumberStr, 10); //정수로만 바꾸기 + + this.validateWinningNumbers(winningNumbers, bonusNumber); // 유효성 검사 + + return { winningNumbers, bonusNumber }; + } + + // 순회하면 유효성 검사 + validateWinningNumbers(winningNumbers, bonusNumber){ + if (winningNumbers.length !== 6 || new Set(winningNumbers).size !== 6){ + throw new Error("[ERROR] 당첨 번호는 1부터 45 사이의 중복되지 않는 숫자 6개여야 합니다."); + } + if (!this.isValidLottoNumber(bonusNumber)){ + throw new Error("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다."); + } + winningNumbers.forEach(num =>{ + if(!this.isValidLottoNumber(num)){ + throw new Error("[ERROR] 당첨 번호는 1부터 45 사이의 숫자여야 합니다. "); + } + }); + } + + isValidLottoNumber(num) { + return num >= 1 && num <= 45; + } + + printPurchasedLottos(lottos) { + MissionUtils.Console.print(`${lottos.length}개를 구매했습니다.`); + lottos.forEach(lotto => { + MissionUtils.Console.print(`[${lotto.getNumbers().sort((a,b) => a - b).join(", ")}]`); // 오름차순으로 출력된다. + }); + } + + printLottoStatistics(statistics, purchaseAmount) { + MissionUtils.Console.print("당첨 통계"); + MissionUtils.Console.print("-----------"); + + Object.entries(statistics.rankCounts).forEach(([rank,count]) => { + MissionUtils.Console.print(`${rank}등: ${count}개`); + }); + + const profitRate = this.lottoResult.calculateProfit(purchaseAmount); + MissionUtils.Console.print(`총 수익률은 ${profitRate}%입니다.`); + } +} + +export default LottoGame; From a892e006e8da2a9fb9ea6825863bf74cbf32b79d Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:22:07 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat(LottoResult):=20=ED=86=B5=EA=B3=84?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LottoResult.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/LottoResult.js diff --git a/src/LottoResult.js b/src/LottoResult.js new file mode 100644 index 000000000..5a827df36 --- /dev/null +++ b/src/LottoResult.js @@ -0,0 +1,60 @@ +// 당첨 여부를 계산하는 역할 +import { PRIZE_TABLE } from "./Constants"; + +class LottoResult { + constructor() { + this.rankCounts = { + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + }; + this.totalProfit = 0; + } + + calculateRank(purchasedLottos, winningNumbers, bonusNumber){ + purchasedLottos.forEach(lotto => { + const matchCount = this.countMatchingNumbers(lotto.getNumbers(), winningNumbers); //matchcount + const isBonusMatched = lotto.getNumbers().includes(bonusNumber); //isBonusMatched + + const rank = this.getRank(matchCount, isBonusMatched); + if(rank) { + this.rankCounts[rank] += 1; + this.totalProfit += this.getPrize(rank); + } + }); + } + + countMatchingNumbers(lottoNumbers, winningNumbers) { + return lottoNumbers.filter(num => winningNumbers.includes(num)).length; //includes() 포함되어 있는 지 확인 + } // 매칭된 수 반환 + + getRank(matchCount,isBonusMatched){ + if(matchCount === 6) return 1; + if(matchCount === 5 && isBonusMatched) return 2; + if(matchCount === 5) return 3; + if(matchCount === 4) return 4; + if(matchCount === 3) return 5; + return null; + } + + getPrize(rank) { + return PRIZE_TABLE[rank] || 0; + } + + calculateProfit(purchaseAmount) { + const profitRate = (this.totalProfit / purchadeAmount) * 100; + return profitRate.toFixed(1); // 소수점 1자리까지 반올림 + } + + getStatistics(){ + return { + rankCounts: this.rankCounts, + totalProfit: this.totalProfit, + }; + } + +} + +export default LottoResult; \ No newline at end of file From 14b7cbdbdec96df8f99659b62e99debe2e8ac915 Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 22:22:50 +0900 Subject: [PATCH 09/10] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=8B=9C=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 872380c9c..9c158e048 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -1,6 +1,7 @@ import App from "../src/App.js"; import { MissionUtils } from "@woowacourse/mission-utils"; +// 입력값을 미리 설정하기 위해 MissionUtils.Console.readLineAsync 를 모킹 ( 제공된 입력값들을 순차적으로 반환하도록 설정) const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -11,19 +12,21 @@ const mockQuestions = (inputs) => { }); }; +// 로또 번호를 미리 설정하기 위해 MissionUtils.Random.pickUniqueNumbersInRange 메서드를 모킹 const mockRandoms = (numbers) => { MissionUtils.Random.pickUniqueNumbersInRange = jest.fn(); numbers.reduce((acc, number) => { return acc.mockReturnValueOnce(number); }, MissionUtils.Random.pickUniqueNumbersInRange); }; - +// MissionUtils.Console.print메서드를 감시하는 logSpy 객체를 만든다. 실제 출력된 로그를 확인하고 검증할 수 있다. const getLogSpy = () => { const logSpy = jest.spyOn(MissionUtils.Console, "print"); logSpy.mockClear(); return logSpy; }; +// 특정 입력값을 주고 실행한 후 오류 메시지를 검증하는 함수 const runException = async (input) => { // given const logSpy = getLogSpy(); From c3f60c6f1d26525dd023d85169dee5823426a5fd Mon Sep 17 00:00:00 2001 From: mejin Date: Mon, 4 Nov 2024 23:56:21 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ConsoleHandler.js | 13 +++++++++++++ src/Lotto.js | 28 +++++++++++++++++++--------- src/LottoGame.js | 10 ++++++---- src/LottoMachine.js | 15 ++++++++++----- src/LottoResult.js | 8 ++++---- 5 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 src/ConsoleHandler.js diff --git a/src/ConsoleHandler.js b/src/ConsoleHandler.js new file mode 100644 index 000000000..025e19a1b --- /dev/null +++ b/src/ConsoleHandler.js @@ -0,0 +1,13 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; +//사용자 입출력 처리 모듈 +class ConsoleHandler { + static async readLineAsync(prompt) { + return await MissionUtils.Console.readLineAsync(prompt); + } + + static print(message) { + MissionUtils.Console.print(message); + } +} + +export default ConsoleHandler; \ No newline at end of file diff --git a/src/Lotto.js b/src/Lotto.js index 183655783..3a2434a3b 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,23 +1,33 @@ -// 로또 티켓을 생성하고 번호를 관리하는 클래스 +// Lotto 클래스를 사용하여 로또 번호를 관리하는 클래스 class Lotto { - #numbers; // 프라이빗 필드로 선언 + #numbers; // 프라이빗 필드로 numbers 선언 constructor(numbers) { this.#validate(numbers); // 유효성 검사 - this.#numbers = numbers; //인스턴스 변수에 저장 + this.#numbers = numbers; // 유효한 경우에만 필드에 할당 } + // numbers 배열이 유효한지 확인하는 메서드 #validate(numbers) { - if (numbers.length !== 6 || !numbers.every(num => num >= 1 && num <= 45)) { - throw new Error("[ERROR] 로또 번호는 1 ~ 45 사이의 숫자 6개여야 합니다."); + if (!Array.isArray(numbers)) { + throw new Error("[ERROR] 로또 번호는 배열이어야 합니다."); + } + if (numbers.length !== 6) { + throw new Error("[ERROR] 로또 번호는 6개의 숫자여야 합니다."); + } + if (!numbers.every(num => typeof num === 'number' && num >= 1 && num <= 45)) { + throw new Error("[ERROR] 로또 번호는 1 ~ 45 사이의 숫자여야 합니다."); + } + if (new Set(numbers).size !== numbers.length) { + throw new Error("[ERROR] 로또 번호는 중복되지 않는 숫자여야 합니다."); } } - // 프라이빗 필드에 접근할 수 있는 안전한 인터페이스를 제공 - getNumbers(){ - return this.#numbers; // #numbers 를 외부에 반환하여, 클래스 외부에서 로또 번호 목록을 읽을 수 있도록 한다. + // numbers 필드에 저장된 로또 번호를 반환하는 메서드 + getNumbers() { + return [...this.#numbers]; // numbers 배열의 복사본을 반환하여 외부에서의 변경을 방지 } - // TODO: 추가 기능 구현 } +// Lotto 클래스를 외부에서 사용할 수 있도록 export export default Lotto; diff --git a/src/LottoGame.js b/src/LottoGame.js index 058df8957..bae8b7ab6 100644 --- a/src/LottoGame.js +++ b/src/LottoGame.js @@ -19,6 +19,7 @@ class LottoGame { // 로또 발행 const purchasedLottos = this.purchaseLottos(amount); + this.printPurchasedLottos(purchasedLottos); // 당첨 번호 및 보너스 번호 입력 @@ -48,12 +49,12 @@ class LottoGame { purchaseLottos(amount){ //구매한 로또 배열 반환 const lottoCount = amount / LOTTO_PRICE; + const lottos = []; // 스택에 저장 - + for(let i = 0; i < lottoCount; i++){ const numbers = this.lottoMachine.generateLotto(); // 로또 번호 생성 - const lotto = new Lotto(numbers); //Lotto 인스턴스 생성(유효성 검사는 Lotto 클래스가 수행) - lottos.push(lotto); + lottos.push(numbers); } return lottos; @@ -95,7 +96,8 @@ class LottoGame { printPurchasedLottos(lottos) { MissionUtils.Console.print(`${lottos.length}개를 구매했습니다.`); lottos.forEach(lotto => { - MissionUtils.Console.print(`[${lotto.getNumbers().sort((a,b) => a - b).join(", ")}]`); // 오름차순으로 출력된다. + + MissionUtils.Console.print(`[${lotto.sort((a,b) => a - b).join(", ")}]`); // 오름차순으로 출력된다. }); } diff --git a/src/LottoMachine.js b/src/LottoMachine.js index f66dac43f..22b7cba6f 100644 --- a/src/LottoMachine.js +++ b/src/LottoMachine.js @@ -1,13 +1,18 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; // 로또 티켓과 당첨 번호를 생성하는 역할 + class LottoMachine { generateLotto() { - const numbers = MissionUtils.Random.pickUniqueNumbersinRange(1, 45, 6); - return new Lotto(numbers); + const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6); + + return numbers; } generateWinningNumbers(){ - const winningNumbers = MissionUtils.Random.pickUniqueNumbersinRange(1,45,6); - const bonusNumber = MissionUtils.Random.pickUniqueNumbersinRange(1,45,1)[0]; + const winningNumbers = MissionUtils.Random.pickUniqueNumbersInRange(1,45,6); + const bonusNumber = MissionUtils.Random.pickUniqueNumbersInRange(1,45,1)[0]; return {winningNumbers, bonusNumber}; } - } \ No newline at end of file +} +export default LottoMachine; \ No newline at end of file diff --git a/src/LottoResult.js b/src/LottoResult.js index 5a827df36..f9f334ade 100644 --- a/src/LottoResult.js +++ b/src/LottoResult.js @@ -1,5 +1,5 @@ // 당첨 여부를 계산하는 역할 -import { PRIZE_TABLE } from "./Constants"; +import { PRIZE_TABLE } from "./Constants.js"; class LottoResult { constructor() { @@ -15,8 +15,8 @@ class LottoResult { calculateRank(purchasedLottos, winningNumbers, bonusNumber){ purchasedLottos.forEach(lotto => { - const matchCount = this.countMatchingNumbers(lotto.getNumbers(), winningNumbers); //matchcount - const isBonusMatched = lotto.getNumbers().includes(bonusNumber); //isBonusMatched + const matchCount = this.countMatchingNumbers(lotto, winningNumbers); //matchcount + const isBonusMatched = lotto.includes(bonusNumber); //isBonusMatched const rank = this.getRank(matchCount, isBonusMatched); if(rank) { @@ -44,7 +44,7 @@ class LottoResult { } calculateProfit(purchaseAmount) { - const profitRate = (this.totalProfit / purchadeAmount) * 100; + const profitRate = (this.totalProfit / purchaseAmount) * 100; return profitRate.toFixed(1); // 소수점 1자리까지 반올림 }