🔥 연습문제 - 제곱근 함수 구현하기

621자
7분

함수와 반복문을 연습하는 방법으로, 제곱근 함수를 구현해 봅시다. 주어진 숫자 x에 대해, z²이 x에 가장 근접한 숫자 z를 찾는 것이 목표입니다.

컴퓨터는 일반적으로 반복문을 사용하여 x의 제곱근을 계산합니다. 어떤 추측값 z로 시작하여, z²이 x에 얼마나 가까운지에 따라 z를 조정하여 더 나은 추측값을 만들어 냅니다:

z -= (z*z - x) / (2*z)
 
go

이 조정을 반복하면 추측값이 점점 더 좋아져서, 실제 제곱근에 가능한 한 가까운 답에 도달하게 됩니다.

이를 제공된 func Sqrt에 구현해 봅시다. z의 초기 추측값으로는 입력값에 상관없이 1이 적당합니다. 먼저 계산을 10번 반복하고 그 과정에서 각 z 값을 출력해 봅시다. 다양한 x 값(1, 2, 3, ...)에 대해 실제 답에 얼마나 가까워지는지, 그리고 추측값이 얼마나 빨리 개선되는지 확인해 보세요.

힌트: 부동 소수점 값을 선언하고 초기화하려면 부동 소수점 구문을 사용하거나 변환을 사용하세요:

z := 1.0
z := float64(1)
 
go

다음으로, 값이 변하지 않을 때까지(또는 아주 작은 양만 변할 때까지) 반복문 조건을 변경해 보세요. 그것이 10번의 반복보다 더 많은지 적은지 확인해 보세요. z에 대한 다른 초기 추측값, 예를 들어 x나 x/2를 시도해 보세요. 여러분의 함수 결과가 표준 라이브러리의 math.Sqrt와 얼마나 가까운지 확인해 보세요.

(참고: 알고리즘의 세부 사항에 관심이 있다면, 위의 z² - x는 z²이 있어야 할 위치(x)에서 얼마나 떨어져 있는지를 나타내고, 2z로 나누는 것은 z²의 변화율에 따라 z를 얼마나 조정할지를 스케일링하기 위한 z²의 도함수입니다. 이런 일반적인 접근 방식을 뉴턴 방법이라고 합니다. 많은 함수에 대해 잘 작동하지만 특히 제곱근에 대해 잘 작동합니다.)

package main
 
import (
	"fmt"
)
 
func Sqrt(x float64) float64 {
	z := 1.0 // 초기 추측값 설정
	for i := 0; i < 10; i++ { // 10번 반복
		z -= (z*z - x) / (2*z) // 뉴턴 방법으로 z 값 조정
		fmt.Printf("Iteration %d, z = %.10f\n", i, z) // 각 반복에서 z 값 출력
	}
	return z // 최종 z 값 반환
}
 
func main() {
	fmt.Printf("Square root of 2 is approximately %.10f\n", Sqrt(2))
}
 
go

위 코드에서는 초기 추측값으로 1.0을 사용하고, 10번의 반복을 수행하면서 각 반복에서 z 값을 출력합니다. 최종적으로 계산된 z 값을 반환하여 main 함수에서 출력합니다.

이제 반복문 조건을 변경하여 z 값의 변화가 충분히 작아질 때까지 반복하도록 해 봅시다:

func Sqrt(x float64) float64 {
	z := 1.0
	prevZ := 0.0
	for {
		z -= (z*z - x) / (2*z)
		if abs(z - prevZ) < 1e-10 { // z 값의 변화가 충분히 작으면 반복 종료
			break
		}
		prevZ = z
	}
	return z
}
 
func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}
 
go

여기서는 이전 z 값을 저장하는 prevZ 변수를 도입하고, 현재 z 값과 이전 z 값의 차이의 절댓값이 1e-10보다 작아질 때까지 반복합니다. 이를 위해 abs 함수를 추가로 정의하였습니다.

이제 다양한 x 값과 초기 추측값에 대해 함수를 테스트해 보고, 표준 라이브러리의 math.Sqrt 함수와 비교해 봅시다:

package main
 
import (
	"fmt"
	"math"
)
 
func Sqrt(x float64) float64 {
	z := 1.0
	prevZ := 0.0
	for {
		z -= (z*z - x) / (2*z)
		if abs(z - prevZ) < 1e-10 {
			break
		}
		prevZ = z
	}
	return z
}
 
func abs(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}
 
func main() {
	values := []float64{1, 2, 3, 4, 5, 10, 100}
	for _, x := range values {
		fmt.Printf("x = %f\n", x)
		fmt.Printf("  Our Sqrt: %.10f\n", Sqrt(x))
		fmt.Printf("  math.Sqrt: %.10f\n", math.Sqrt(x))
		fmt.Printf("  Difference: %.10f\n", abs(Sqrt(x)-math.Sqrt(x)))
		fmt.Println()
	}
}
 
go

위 코드는 다양한 x 값에 대해 우리가 구현한 Sqrt 함수와 math.Sqrt 함수의 결과를 비교하여 출력합니다. 여러분의 함수가 표준 라이브러리 함수와 얼마나 유사한 결과를 내는지 확인해 보세요.

실행 결과를 보면 우리의 Sqrt 함수가 math.Sqrt 함수와 매우 유사한 결과를 내는 것을 알 수 있습니다. 차이는 대부분 10^-10 이하입니다.

이렇게 함수와 반복문을 활용하여 제곱근을 계산하는 함수를 구현해 보았습니다. 뉴턴 방법을 사용하여 효율적으로 근사값을 찾아가는 과정을 경험해 볼 수 있었습니다. 여러분도 다양한 값과 초기 추측값으로 실험해 보면서 함수의 동작을 더 깊이 이해해 보시기 바랍니다!