티스토리 뷰

Swift

Swift ) FloatingPoint

Zedd0202 2018. 4. 14. 15:13
반응형

안녕하세요 :) Zedd입니다.



Apple문서 파도타기 하면 진짜..재밌는걸 많이 발견하는 것 같아요. XD...

그래서 재밌는 걸 발견해서 정리해보려고 합니다. :)

바로 FloatingPoint인데요, 말 그자체에서 볼 수 있듯이.. 뭔가 소수느낌? 



FloatingPoint



FloatingPoint는 프로토콜인데요,

정의는 "부동 소수점 숫자 타입"이네요.


부동 소수점 타입은 5.5, 100.0 또는 3.141592와 같은 소수를 나타내는데 사용됩니다. 각 부동 소수점 타입에는 고유 한 가능한 범위와 정밀도(precision)가 있습니다. 표준 라이브러리의 부동 소수점 타입은 사용 가능한 경우 Float, Double 및 Float80입니다. 

정수 또는 부동 소수점 리터럴을 사용하여 부동 소수점 타입의 새 인스턴스를 만듭니다.

let temperature = 33.2
let recordHigh = 37.5

이렇게요.


FloatingPoint프로토콜은 일반적인 산술 연산을 선언하므로, 모든 부동소수점 타입에서 작동하는 함수와 알고리즘을 작성 할 수 있습니다.

다음 예제에서는 직각 삼각형의 빗변 길이를 계산하는 함수를 선언합니다. hypotenuse (_ : _ :) 함수는 FloatingPoint 프로토콜에 제약 된 일반 매개 변수를 사용하기 때문에 부동 소수점 유형을 사용하여 호출 할 수 있습니다.



func hypotenuse<T: FloatingPoint>(_ a: T, _ b: T) -> T {
    return (a * a + b * b).squareRoot()
}

let (dx, dy) = (3.0, 4.0)
let distance = hypotenuse(dx, dy)
// distance == 5.0


Generic에서 배웠죠? T가 궁금하시다면, Generic글을 참고해주세요 :)

이렇게 되면, 저 hypotenuse함수는 FloatingPoint프로토콜을 준수하는 것들만이 파라미터로 들어갈 수 있겠죠.


부동 소수점 값은 부호 및 크기(magnitude)로 표시되며, 크기는 타입의 radix와 인스턴스의 significand 및 지수(exponent)를 사용하여 계산됩니다.

이 크기 계산은 F 유형의 부동 소수점 값 x에 대해 다음 형식을 취합니다. 여기서 **는 지수입니다.

x.significand * F.radix ** x.exponent

다음은 2진수를 정의하는 Double타입의 인스턴스로 표현된 숫자 -8.5의 예입니다.
let y = -8.5
// y.sign == .minus
// y.significand == 1.0625
// y.exponent == 3

let magnitude = 1.0625 * Double(2 ** 3)
// magnitude == 8.5

와...1시간동안 왜 y.significand가 1.0625인지...아..아니 역시 안하면 까먹는구나..중학교때는 기계적으로 했던 것 같은데...

혹시나 이게 궁금하신 분들을 위해 적을게요 :)
이걸 이해하려면..이 부동소수점에 대한 이해가 필요한데, 
정의는 "실수를 표현할 때, 그 위치를 나타내는 수를 따로 적는 것으로, 유효숫자를 나타내는 가수와 소수점의 위치를 풀이하는 지수로 나누어 표현한다.

  • 부호부 (1비트) : 양수일 때는 0, 음수일 때는 1
  • 지수부 (부호가 있는 정수, 7비트) : 제일 앞의 1비트는 부호를 정하고, 나머지 6비트로 표시
  • 정규화된 가수부 (부호가 없는 정수, 24비트) : 제일 앞의 비트는 정규화되었으므로 1이다.

부동소수점은 10진법이 아닌 2진법의 부동소수점 표기 방법인데, 예를들어, 127.375라는 수는 내부적으로 표현할 때는 1.27375 * 102이 아니라, 이 값의 2진수에 해당하는 1.1111110112 * 26으로 표현한다는 말입니다.

그럼 -8.5의 significand가 왜 1.0625인지 보러 갑시다.


이걸 그대로 2진법으로 나타내면, 일단 8 => 1000(2)

0.5 => 1(2)

즉, -8.5는 1000.1이 됩니다. (-가 왜 없냐? 부호부 비트를 1로 주면 되니까..일단 부호비트는 생각하지 않을게요. 부호비트는 1입니다. 음수니까. 양수면 0이죠?)

(소수점을 2진법으로 고치는 방법은...설명하진 않을게요 :) 1이 될 때 까지 2를 계속 곱해나가면 됩니다.)


그리고 정규화 과정을 거쳐야 하는데요, 왼쪽에는 1만 남게 하는 것이죠.

1.0001 * 2^3


그리고 2진법에서의 소수점은..!

앞에서부터 2의 -1, -2..이런식으로 해나가야 하죠?

즉 .0001은 결국 2^-4가 됩니다. 즉 1/16이죠. 1/16은???? 네 0.0625입니다.


그래서!!!!!!!!!!!!!!!!!!!!!!!!!!!

-8.5의 significand는 1.0625가 되는것이죠.

1.0001 * 2^3여기서

radix는 2고 exponent는 3이니,

x.significand * F.radix ** x.exponent

에 의해. 

-8.5의 "크기"는 

 
let magnitude = 1.0625 * Double(2 ** 3)
// magnitude == 8.5


8.5가 됩니다.



ㅎㅎ..솔직히 이해 안가셔도 상관없다고 생각해용..

컴퓨터가 알아서 다 계산해주니깐..


Additional Considerations

특정 숫자를 나타내는 것 이외에도, 부동 소수점 타입에는 오버플로 및 숫자가 아닌 계산결과로 작업하기 위한 특별한 값이 있습니다.

● Infinity

크기가 너무 커서 표현 가능한 범위 밖으로 값이 반올림되는 값은 반올림되어 무한대(Infinity)로 됩니다.
타입 F에 대하여, 양의 무한대는 각각 F.infinity 및 음의 무한대는 각각 -F.infinity로 표현됩니다.
양의 무한대는 모든 유한한 값과 음의 무한대보다 큽니다. 반면 음의 무한대는 모든 유한한 값과 양의 무한대보다 작습니다.
동일한 부호를 갖는 무한값은 서로 동일합니다.
let values: [Double] = [10.0, 25.0, -10.0, .infinity, -.infinity]
print(values.sorted())
// Prints "[-inf, -10.0, 10.0, 25.0, inf]"

무한 값의 연산은 가능한 한 실제 산술 연산을 따릅니다. 유한 값을 더하거나 빼거나, 무한대를 0이 아닌 유한한 값으로 곱하거나 나누면 결과는 무한대가 됩니다.

● NaN (“not a number”)

부동 소수점 타입은 "숫자가 아닌(not a number)"의 약자인 NaN과 같이 유한한 수나 무한대가 아닌 값을 나타냅니다.
NaN을 다른 값과 비교하면 결과가  false가 됩니다. 
let myNaN = Double.nan
print(myNaN > 0)
// Prints "false"
print(myNaN < 0)
// Prints "false"
print(myNaN == .nan)
// Prints "false"

마지막줄이 이해가 안갈 수도 있죠. myNaN은 분명 Doble의 nan인데, 왜 false가 나오지? true아냐?
아닙니다. 
NaN을 다른값과 비교하면, 다른 NaN을 포함하여 결과가 false로 됩니다.

하나의 NaN이 다른 NaN과 동일한지 여부를 테스트하면 false가 되므로, isNaN프로퍼티를 사용하여 값이 NaN인지 테스트합니다.
print(myNaN.isNaN)
// Prints "true"


NaN은 많은 산술 연산을 통해 전파(propagates)됩니다. 많은 값으로 조작하고 있는 경우, NaN의 조작으로 값이 전송되기 때문에, 실행시의 에러는 발생하지 않습니다. 그렇기 때문에 이 동작은 중요합니다. 다음 예제에서는 NaN값이 다른 컨텍스트에서 작동하는 방식을 보여줍니다. 


let temperatureData = ["21.5", "19.25", "27", "no data", "28.25", "no data", "23"]
let tempsCelsius = temperatureData.map { Double($0) ?? .nan }
// tempsCelsius == [21.5, 19.25, 27, nan, 28.25, nan, 23.0]

temperatureData 배열의 일부 요소는 유효한 숫자가 아닙니다. 이러한 유효하지 않은 문자열이 Double fiable 이니셜 라이저에 의해 파싱되면이 예에서는 nil-coalescing 연산자 (??)를 사용하여 폴백 값으로 NaN을 제공합니다.

다음으로, 섭씨의 관측치가 화씨로 변환됩니다.

let tempsFahrenheit = tempsCelsius.map { $0 * 1.8 + 32 }
// tempsFahrenheit == [70.7, 66.65, 80.6, nan, 82.85, nan, 73.4]

tempsCelsius 배열의 NaN 값은 변환을 통해 전파되고 tempsFahrenheit에 NaN으로 유지됩니다.

관측치의 평균을 계산할 때 tempsFahrenheit 배열의 모든 값을 결합해야하므로이 예제에서와 같이 모든 NaN 값으로 인해 NaN이 반환됩니다.

let badAverage = tempsFahrenheit.reduce(0.0, combine: +) / Double(tempsFahrenheit.count)
// badAverage.isNaN == true

대신에 특정 수치 결과를 얻고, 연산이 필요하면 isNaN프로퍼티를 이용하여 NaN값을 필터링 하십시오.

let validTemps = tempsFahrenheit.filter { !$0.isNaN }
let average = validTemps.reduce(0.0, combine: +) / Double(validTemps.count)

솔직히 이까지 읽으면..이게 왜...싶죠..



이 FloatingPoint의 인스턴스 프로퍼티는 정말정말 많은데..정말 재밌어요. 인스턴스 프로퍼티는 별로 큰 재미는 없ㅈ디만...저는 이 FloatingPoint의 인스턴스 메소드가 알아놓으면 조금 재밌을 것 같아서 이 FloatingPoint를 공부하는 거에요 :)

일단 인스턴스 프로퍼티부터 해봅시다.



FloatingPoint - Instance Property




●  exponentThe exponent of the floating-point value.

말그래도 Float소수의 exponent. 지수입니다.


let y: Double = 21.5
// y.significand == 1.34375
// y.exponent == 4
// Double.radix == 2

위에서도 했지만...이해가 안되시는 분들을 위해 풀이과정을 간단하게나마...써봤습니다.
exponent는 재밌는게 하나 있는데, 

print(0.0.exponent)//-9223372036854775808

print(Float.infinity.exponent, Float.nan.exponent)//9223372036854775807


0.0. 즉 zero의 exponent는 Int.min이며,  +/-infinity 또는 NaN일때는 exponent는 Int.max가 됩니다.


● floatingPointClass

The classification of this value.

말그대로 값의 분류입니다.
이 floatingPointClass의 타입은 FloatingPointClassification인데요, enum입니다!

Enumeration Cases

case negativeInfinity

A value equal to -infinity.

case negativeNormal

A negative value that uses the full precision of the floating-point type.

case negativeSubnormal

A negative, nonzero number that does not use the full precision of the floating-point type.

case negativeZero

A value equal to zero with a negative sign.

case positiveInfinity

A value equal to +infinity.

case positiveNormal

A positive value that uses the full precision of the floating-point type.

case positiveSubnormal

A positive, nonzero number that does not use the full precision of the floating-point type.

case positiveZero

A value equal to zero with a positive sign.

case quietNaN

A silent NaN (“not a number”) value.

case signalingNaN

A signaling NaN (“not a number”).

이렇게나 많은 Case들이 존재합니다. 간단한거니까 하나하나 보진 않을게요. 정말 설명이 다인..그런 것들이라.

● isCanonical

인스턴스의 표현이 표준 형식인지 여부를 나타내는 부울 값입니다.

말그대로 isCanonical입니다. 너 표준이니?


모든 Float 또는 Double 값은 표준이지만 Float80 타입의 비표준(noncanonical) 값이 존재하며 비표준 값이 FloatingPoint 프로토콜을 준수하는 다른 타입에 대해 존재할 수 있습니다.

음..사실 이건 뭐...딱히..정말 쓸 것 같지 않은 그런 프로퍼티...Float80이라고 전부 비표준이 아니라, Float80안에 비표준 값이 있을 수 있다는 거에요! 해당 Float80에 대해 isCanonical을 호출하면 false가 나오겠죠?


뭐가 false인지는 모르겠음ㅎ

● isFinite

이 인스턴스가 유한한지 여부를 나타내는 Bool 값.

NaN 및 무한대 이외의 모든 값은 normal, subnormal에 관계없이 유한 값으로 간주됩니다.

● isInfinite

인스턴스가 무한인지 여부를 나타내는 Bool 값.
isFinite와 isInfinite는 total이 아니기 때문에 이분법(dichotomy)을 형성하지 않습니다. x가 NaN이면 두 속성이 모두 거짓입니다.

●  isNaN

인스턴스가 NaN ( "숫자가 아님")인지 여부를 나타내는 Bool 값.
NaN은 NaN을 비롯한 어떤 값과도 같지 않으므로 equal-to 연산자 (==) 또는 not-equal-to 연산자 (! =) 대신 이 프로퍼티를 사용하여 값이 NaN인지 여부를 테스트합니다. 

let x = 0.0
let y = x * .infinity
// y is a NaN

// Comparing with the equal-to operator never returns 'true'
print(x == Double.nan)
// Prints "false"
print(y == Double.nan)
// Prints "false"

// Test with the 'isNaN' property instead
print(x.isNaN)
// Prints "false"
print(y.isNaN)
// Prints "true"

이 프로퍼티(isNaN)은 quiet NaN 및 signaling NaN 모두에 적용됩니다.

case quietNaN

A silent NaN (“not a number”) value.

case signalingNaN

A signaling NaN (“not a number”).




위에서 

floatingPointClass의 case였죠.

NaN의 핵심은 “값이 없다”이기 때문에, 다른 NaN과도 같지 않다는 것을 기억해야 해요.


●  isNormal

이 인스턴스가 Normal인지 여부를 나타내는 부울 값입니다.
normal 값은 타입의 값에 사용할 수있는 완전한 정밀도를 사용하는 유한한 수입니다. 0은 normal 또는 subnormal 숫자가 아닙니다.
(계속 normal이랑 subnormal이 뭐야..;라고 궁금증이 드실텐데..이게 무슨 대수학에서 쓰는...잘 모르겠습니다. 하지만 음수가 subnormal이고 양수가 normal이고 이런건 아니에요!!!!!)



● isSignalingNaN


인스턴스가 Signaling NaN인지 여부를 나타내는 Bool 값.

(isQuiteNaN은 없네...)

QNaN Quite NaN 약자로, QNaN 연산에서 피연산자나 결과로 쓰여도 정상적인 연산처럼 동작하도록 하는 경우이고SNaN Signaling NaN 약자로, SNaN 피연산자로 쓰이거게 되면 CPU에서 Exception 에러를 내게 됩니다.

이중 0으로 나누는 경우와 같은, 수학적으로 정의되지 않은 연산에 대해서는 QNaN 결과로 가지도록 규정하고 있습니다. 

,  0으로 나누는 연산은 표준안에서 정의된 결과이기 때문에 CPU에서는 에러를 내지 않고 정상적인 값처럼 동작하는 것입니다.



하지만 왜  Swift에서는 inf지....

Signaling NaN은 일반적으로 일반적인 컴퓨팅 연산에서 사용되면 Invalid 플래그를 발생시킵니다.

● isSubnormal

인스턴스가 subnormal인지 여부를 나타내는 Bool값.

subnormal(비정규)값은 가장 작은 일반적은 숫자보다 작은 크기의 0이 아닌 숫자입니다.(뭐라는건지...A subnormal value is a nonzero number that has a lesser magnitude than the smallest normal number.)

subnorma값은 타입 값에 사용 할 수 있는 전체 정밀도를 사용하지 않습니다. 0은 normal 또는 subnormal인 숫자가 아닙니다. subnorma은 종종 denormal또는 denormalized라고도 합니다. 이들은 동일한 개념의 다른 이름입니다.(어쩐지 subnormal쳤는데 denormal이 나오더라..)


● isZero

이름부터 넘나 쉬운것
인스턴스가 0인지 여부를 나타내는 Bool 값.
 isZero 프로퍼티 값이 -0.0 또는 +0.0을 나타내는 경우 true입니다. x.isZero는 다음 비교와 같습니다. x == 0.0.

let x = -0.0
x.isZero        // true
x == 0.0        // true

● nextDown


드디어 is~가 끝났습니다...이번부터는 조금 헷갈릴 수 있는 개념들이 나와요..!
이 값보다 작은 나타낼 수 있는(representable) 값 중에서 가장 큰 값.


도댜ㅐ체......뭔말..

어떤 유한(finite) 값 x에 대해서 x.nextDown은 x보다 작습니다. nan 또는 -infinity의 경우 x.nextDown은 x 자체입니다. 다음 특수 사례도 적용됩니다.


  • If x is infinity, then x.nextDown is greatestFiniteMagnitude.

  • If x is leastNonzeroMagnitude, then x.nextDown is 0.0.

  • If x is zero, then x.nextDown is -leastNonzeroMagnitude.

  • If x is -greatestFiniteMagnitude, then x.nextDown is -infinity.



greatestFiniteMagnitude랑 leastNonzeroMagnitude이런게 지금 나오다니...일단 FloatingPoint의 타입 프로퍼티입니다...

아무튼 저도 이 nextDown을 왜 쓰는지 모르겠는데요...



바로 이런것....

a에 3.1의 nextDown해서 넣었지만..



a는 출력은 3.1...

하지만 저 위 코드에서 3.1와 a를 비교했을 때는 false로 나왔으니 확실히 3.1은 아닌 것을 알 수 있어요.

nextDown이니까 3.1보다 작겠죠?


오..실험해보니까



a는 사ㅏㅏ실은 3.09지만 나올때는 3.1로 나오네요.

암튼 다름!!!!


● nextUp

 nextDown이 있으면..nextUp도..있는 법......

뭔지는 감이 오시죠?

딱딱한 정의부터 봅시다.


The least representable value that compares greater than this value. 한글로 쓰니까 더 헷갈리실 것 같아서 걍 영어로..쓰겠습니다.


임의의 유한(finite) 값 x에 대하여, x.nextUp는 x보다 큽니다. nan 또는 무한대의 경우 x.nextUp는 x 자체입니다. 다음 특수 사례도 적용됩니다.


  • If x is -infinity, then x.nextUp is -greatestFiniteMagnitude.

  • If x is -leastNonzeroMagnitude, then x.nextUp is -0.0.

  • If x is zero, then x.nextUp is leastNonzeroMagnitude.

  • If x is greatestFiniteMagnitude, then x.nextUp is infinity.




이제는 a가 3.1보다 커졌습니다~.~


● sign

부동 소수점 값의 부호입니다.

값의 부호가 설정되면 sign 프로퍼티는 .minus이고, 그렇지 않으면 .plus입니다. 

let x = -33.375

// x.sign == .minus

이 프로퍼티를 사용하여 부동 소수점 값이 음수인지 여부를 확인하지 마십시오. 값 x의 경우,  x.sign == .minus는 반드시 x <0과 같을 필요는 없습니다. 특히 x.sign == .minus는 x가 -0이고 x < 0이면 x는 항상 false입니다. x가 NaN이면 x.sign은 .plus 또는 .minus가 될 수 있습니다. 

●  significand

부동 소수점 값의 유효 숫자입니다.

let magnitude = x.significand * F.radix ** x.exponent

위에서 했죠?
구하는 방법도...


저 1.34375가 바로 significand입니다.
  • If x is zero, then x.significand is 0.0. // x가 0이면, x의 significand는 0.0입니다.

  • If x is infinity, then x.significand is 1.0.//x가 infinity이면, x의 significand는 1.0입니다.

  • If x is NaN, then x.significand is NaN.//x가 NaN이면, x의 significand는 NaN입니다.


● ulp

이 값의 마지막 자리에있는 단위(unit)입니다.

이 값의 significand에서 최하위 숫자의 단위(unit)입니다. 대부분의 수 x에 대해, 이것은 x와 그 다음으로 큰 (크기로) 표현 가능한 수 사이의 차이입니다. 알고 있어야 할 몇 가지 중요한 경우가 있습니다.

- x가 유한한 수(finite)가 아닌 경우 x.ulp는 NaN입니다.
- x의 크기가 매우 작으면 x.ulp가 비정규(subnormal) 숫자 일 수 있습니다. 타입이 subnormals을 지원하지 않으면 x.ulp가 0으로 반올림 될 수 있습니다.
- greaterFiniteMagnitude.ulp는 더 큰 다음 값이 무한대 임에도 불구하고 유한수입니다.

이 quantity 또는 related quantity은 종종 epsilon(엡실론) 는 machine epsilon이라고합니다. 그 이름은 다른 언어로 다른 의미를 지니고 있기 때문에 혼동을 야기 할 수 있으며 비교를 위해 사용하는 것은 좋게 허용(tolerance, 허용오차)되지만, 거의 그렇지 않습니다.


이건.....진짜..무슨소리인지 모르겠네요 ㅎㅎ



이렇게 FloatingPoint의 인스턴스 프로퍼티가 끝났습니다..
일단 너무 길어지니까 여기까지...시간 되면 꼭 메소드들 쓸게요!!! 타입 프로퍼티가 남긴 했지만..


반응형

'Swift' 카테고리의 다른 글

Swift 4.2 변경사항  (3) 2018.06.17
Swift ) Class only Protocol. class? AnyObject?  (4) 2018.04.18
Swift 4.1 Released! -2  (0) 2018.04.14
Swift ) Hashable  (1) 2018.04.10
Swift ) Key decoding strategy  (0) 2018.04.08