Go Lang/Study

[GoLang] 자료형을 초과한 큰 수를 계산해보자

DSeung 2023. 9. 1. 17:51

개요

C, Java 등을 배우다 보면 가장 처음에 배우는 게 자료형입니다

저도 고등학교 입학 전 겨울 방학 때 배웠을 때 이 수를 넘어가는 건 어떻게 쓸까라는 생각을 했었죠

그 궁금증을 해결해봅시다 

 

하지만 해당 포스팅에서는 Go를 사용할 것입니다 

 

오버플로우, 언더플로우

그 전에 오버플로우, 언더플로우를 알아야 합니다.

정리하면 자료형이 가진 값(메모리 할당량)을 넘어선 상태입니다.

 

오버플로우 (overflow) : 값이 해당 자료형이 가진 수보다 커진 상태로,  이상 값을 도출하는 상태 

예시 ) 0 ~ 127까지 담을 수 있는 자료형에 128가 들어와서 값이 0이 되는 상태

 

언더플로우 (underflow) : 값이 해당 자료형이 가진 수보다 작아진 상태로, 이상 값을 도출하는 상태 

예시 )  0 ~ 127까지 담을 수 있는 자료형에 -1이 들어와서 값이 127이 되는 상태

 

※ 2000년대 초반 C표준이 개정되면서 둘 다 overflow라 불리며 underflow는 부동소수점과 stack에서만 사용한다는 글이 있지만 옳고 그름을 떠나 통상적인 커뮤니케이션에 맞추는 쪽이 더 좋다고 생각합니다, 그래도 알고 있는 게 좋죠 ※

해당 글 : https://80000coding.oopy.io/10d17093-d9cd-4edb-8d2b-55a4bff86565

 

여러분, underflow(언더플로우)는 틀린 용어랍니다. (정정) → underflow, 그때는 맞고 지금은 틀리다. (

🌸 special thanks to

80000coding.oopy.io

 

Go에서의 Overflow

package main

import "fmt"

func main() {
	// 가장 큰 자료형은 18446744073709551615 까지 가능
	var number1 = uint64(18446744073709551615)
	// 아래 변수는 에러 발생
	var number2 = uint64(18446744073709551616)

	fmt.Println(number1)
	fmt.Println(number2)
}

결과물 

# command-line-arguments
.\main.go:9:23: cannot convert 18446744073709551616 (untyped int constant) to type uint64

 

Go에서의 해결법

이번에 짤 코드는 두 변수를 문자열로 값을 입력받은 다음 두 개다 int slice로 바꾼 다음 더하고 나온 값을 문자열로 바꿔주기에 기존에 uint64의 메모리 할당량을 초과한 값도 받을수 있게 코딩할 것 입니다.

 

폴더 구조

main.go

package main

import (
	"bignumber-cal.com/bignumber"
	"fmt"
)

func main() {
	// uint64가 아닌 문자열을 사용
	var number1 = "18446744073709551"
	var number2 = "18446744073709551616"

	fmt.Println(bignumber.AddLargeNumbers(number1, number2))
}

bignumber/calculator.go

package bignumber

import (
	"strconv"
)

// AddLargeNumbers : 엄청 큰 숫자도 더할 수 있게 해줌
func AddLargeNumbers(a, b string) string {
	// 두 정수를 문자열에서 int 슬라이스로 변환
	number1 := stringToIntSlice(a)
	number2 := stringToIntSlice(b)

	// 항상 number1이 더 큰 수로
	if len(number1) < len(number2) {
		number1, number2 = number2, number1
	}

	// 배열 차이만큼 빈 배열을 만들어서 number2에 앞에 추가하기
	number2 = append(make([]int, len(number1)-len(number2)), number2...)

	carry := 0
	result := make([]int, len(number1))

	// 뒤에서부터 계산
	for i := len(number1) - 1; i >= 0; i-- {
		sum := number1[i] + number2[i] + carry
		result[i] = sum % 10
		carry = sum / 10
	}

	// 결과를 문자열로 변환 + 만약 number1과 number2가 같은 자릿수로 carry 값이 생겼을 때 더하기
	var resultString string
	if carry > 0 {
		resultString = strconv.Itoa(carry)
	}
	for _, digit := range result {
		resultString += strconv.Itoa(digit)
	}

	return resultString
}

// stringToIntSlice : 문자열을 숫자 slice로 변경
func stringToIntSlice(s string) []int {
	var result []int
	for _, char := range s {
		digit, _ := strconv.Atoi(string(char))
		result = append(result, digit)
	}
	return result
}

결과물

// 출력
18465190817783261167

math/big 패키지

사실 패키지가 존재해서 구현할 필요는 없습니다, 이걸 사용하면 메모리 할당에 대한 제한이 없이 사용 가능합니다.

참고로 해당 big 패키지도 슬라이스로 해결했습니다

package main

import (
	"fmt"
	"math/big"
)

func main() {
	var number1 = new(big.Int)
	var number2 = new(big.Int)
	var result = new(big.Int)

	// 뒤에 base 파라티머에 경우는 10진수를 의미합니다.
	number1.SetString("18446744073709551", 10)
	number2.SetString("18446744073709551616", 10)

	result.Add(number1, number2)

	fmt.Println(result)
}

둘의 출력 값은 똑같습니다.

 

마무리

오타 및 피드백(개선방안 또는 다른 스타일의 코드)은 언제나 환영입니다.

감사합니다. 

반응형