최신 JavaScript에는 두 가지 유형의 숫자가 있습니다.
JavaScript의 일반 숫자는 "이중 정밀도 부동 소수점 숫자"라고도 알려진 64비트 형식 IEEE-754로 저장됩니다. 이것은 우리가 대부분 사용하는 숫자이며, 이 장에서 이에 대해 이야기하겠습니다.
BigInt 숫자는 임의 길이의 정수를 나타냅니다. 앞서 데이터 유형 장에서 언급한 것처럼 일반 정수는 (2 53 -1)
초과하거나 -(2 53 -1)
보다 작을 수 없기 때문에 때때로 필요합니다. bigint는 몇 가지 특별한 영역에서 사용되므로 BigInt라는 특별한 장에 집중합니다.
그래서 여기서는 일반 숫자에 대해 이야기하겠습니다. 그들에 대한 지식을 넓혀 봅시다.
10억을 써야 한다고 상상해 보세요. 확실한 방법은 다음과 같습니다.
10억 = 1000000000이라고 하자.
밑줄 _
구분 기호로 사용할 수도 있습니다.
10억 = 1_000_000_000이라고 하자;
여기서 밑줄 _
은 "구문적 설탕" 역할을 하며 숫자를 더 읽기 쉽게 만듭니다. JavaScript 엔진은 단순히 숫자 사이의 _
무시하므로 위와 정확히 10억이 같습니다.
하지만 실제 생활에서는 0을 길게 연속해서 쓰는 것을 피하려고 노력합니다. 그러기엔 우리는 너무 게으릅니다. 10억은 "1bn"
, 70억 "7.3bn"
과 같이 쓰도록 하겠습니다. 대부분의 큰 숫자에도 마찬가지입니다.
JavaScript에서는 문자 "e"
를 추가하고 0 개수를 지정하여 숫자를 줄일 수 있습니다.
10억 = 1e9라고 하자; // 10억, 문자 그대로: 1과 9개의 0 경고(7.3e9); // 73억(7300000000 또는 7_300_000_000과 동일)
즉, e
주어진 0 개수에 1
곱합니다.
1e3 === 1 * 1000; // e3은 *1000을 의미합니다. 1.23e6 === 1.23 * 1000000; // e6은 *1000000을 의미합니다.
이제 아주 작은 것을 작성해 봅시다. 1마이크로초(100만분의 1초)를 말해보세요.
ms를 0.000001로 설정합니다.
이전과 마찬가지로 "e"
사용하면 도움이 될 수 있습니다. 0을 명시적으로 작성하는 것을 피하려면 다음과 같이 작성할 수 있습니다.
mcs = 1e-6으로 둡니다. // 1에서 왼쪽으로 5개의 0이 있음
0.000001
의 0을 세어보면 6개가 있습니다. 따라서 자연스럽게 1e-6
입니다.
즉, "e"
뒤의 음수는 주어진 수의 0을 사용하여 1로 나누는 것을 의미합니다.
// -3을 1로 나누고 0이 3개 있는 경우 1e-3 === 1/1000; // 0.001 // -6은 6개의 0을 사용하여 1로 나눕니다. 1.23e-6 === 1.23 / 1000000; // 0.00000123 // 더 큰 숫자의 예 1234e-2 === 1234 / 100; // 12.34, 소수점 2번 이동
16진수는 JavaScript에서 색상을 표현하고 문자를 인코딩하는 등의 용도로 널리 사용됩니다. 따라서 당연히 0x
와 숫자를 더 짧게 쓰는 방법이 있습니다.
예를 들어:
경고( 0xff ); // 255 경고(0xFF); // 255 (동일, 대소문자는 중요하지 않음)
2진수와 8진수 체계는 거의 사용되지 않지만 0b
및 0o
접두사를 사용하여도 지원됩니다.
a = 0b11111111로 둡니다. // 255의 이진 형식 b = 0o377로 둡니다. // 255의 8진수 형태 경고( a == b ); // true, 양쪽에 같은 숫자 255가 있습니다.
이러한 지원을 제공하는 숫자 체계는 3개뿐입니다. 다른 숫자 시스템의 경우, parseInt
함수를 사용해야 합니다(이 장의 뒷부분에서 살펴보겠습니다).
num.toString(base)
메소드는 주어진 base
사용하여 기수 체계로 num
의 문자열 표현을 반환합니다.
예를 들어:
숫자 = 255로 설정; 경고( num.toString(16) ); // ff 경고( num.toString(2) ); // 11111111
base
2
에서 36
까지 다양합니다. 기본적으로는 10
입니다.
이에 대한 일반적인 사용 사례는 다음과 같습니다.
base=16은 16진수 색상, 문자 인코딩 등에 사용되며 숫자는 0..9
또는 A..F
일 수 있습니다.
base=2 는 주로 비트 연산 디버깅에 사용되며 숫자는 0
또는 1
일 수 있습니다.
base=36 이 최대값이고 숫자는 0..9
또는 A..Z
일 수 있습니다. 전체 라틴 알파벳은 숫자를 나타내는 데 사용됩니다. 36
의 재미있지만 유용한 사례는 예를 들어 짧은 URL을 만들기 위해 긴 숫자 식별자를 더 짧은 식별자로 변환해야 하는 경우입니다. 36
진수를 사용하여 간단히 표현할 수 있습니다.
경고( 123456..toString(36) ); // 2n9c
메소드 호출을 위한 두 개의 점
123456..toString(36)
의 점 두 개는 오타가 아닙니다. 위의 예에서 toString
과 같이 숫자에 대해 직접 메서드를 호출하려면 그 뒤에 두 개의 점 ..
을 배치해야 합니다.
단일 점( 123456.toString(36)
을 배치하면 JavaScript 구문이 첫 번째 점 뒤의 소수 부분을 암시하기 때문에 오류가 발생합니다. 그리고 점을 하나 더 배치하면 JavaScript는 소수 부분이 비어 있음을 인식하고 이제 해당 메서드를 실행합니다.
(123456).toString(36)
쓸 수도 있습니다.
숫자 작업 시 가장 많이 사용되는 작업 중 하나는 반올림입니다.
반올림을 위한 몇 가지 내장 함수가 있습니다:
Math.floor
반내림: 3.1
은 3
되고, -1.1
은 -2
가 됩니다.
Math.ceil
반올림: 3.1
은 4
가 되고 -1.1
은 -1
이 됩니다.
Math.round
가장 가까운 정수로 반올림됩니다. 3.1
은 3
이 되고, 3.6
4
가 됩니다. 중간의 경우 3.5
4
로 반올림되고, -3.5
-3
으로 반올림됩니다.
Math.trunc
(Internet Explorer에서는 지원되지 않음)
반올림하지 않고 소수점 이하의 모든 항목을 제거합니다. 3.1
은 3
, -1.1
은 -1
이 됩니다.
이들 간의 차이점을 요약한 표는 다음과 같습니다.
Math.floor | Math.ceil | Math.round | Math.trunc | |
---|---|---|---|---|
3.1 | 3 | 4 | 3 | 3 |
3.5 | 3 | 4 | 4 | 3 |
3.6 | 3 | 4 | 4 | 3 |
-1.1 | -2 | -1 | -1 | -1 |
-1.5 | -2 | -1 | -1 | -1 |
-1.6 | -2 | -1 | -2 | -1 |
이 함수는 숫자의 소수 부분을 처리하는 가능한 모든 방법을 다룹니다. 하지만 숫자를 소수점 이하 n-th
자리로 반올림하고 싶다면 어떻게 해야 할까요?
예를 들어 1.2345
있고 이를 2자리로 반올림하여 1.23
만 얻으려고 합니다.
그렇게 하는 방법에는 두 가지가 있습니다.
곱셈과 나눗셈.
예를 들어 숫자를 소수점 이하 두 번째 자리로 반올림하려면 숫자에 100
을 곱하고 반올림 함수를 호출한 다음 다시 나눌 수 있습니다.
숫자 = 1.23456; 경고( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
toFixed(n) 메소드는 숫자를 점 뒤의 n
자리로 반올림하고 결과의 문자열 표현을 반환합니다.
숫자 = 12.34; 경고( num.toFixed(1) ); // "12.3"
이는 Math.round
와 유사하게 가장 가까운 값으로 반올림되거나 내림됩니다.
숫자 = 12.36; 경고( num.toFixed(1) ); // "12.4"
toFixed
의 결과는 문자열이라는 점에 유의하세요. 소수 부분이 필요한 것보다 짧으면 끝에 0이 추가됩니다.
숫자 = 12.34; 경고( num.toFixed(5) ); // "12.34000", 정확히 5자리를 만들기 위해 0을 추가했습니다.
단항 더하기 또는 Number()
호출을 사용하여 이를 숫자로 변환할 수 있습니다(예: +num.toFixed(5)
.
내부적으로 숫자는 64비트 형식인 IEEE-754로 표현되므로 숫자를 저장하는 데 정확히 64비트가 있습니다. 그 중 52비트는 숫자를 저장하는 데 사용되고, 11비트는 소수점 위치를 저장하고, 1비트는 기호용입니다.
숫자가 정말 크면 64비트 저장소를 초과하여 특별한 숫자 값 Infinity
될 수 있습니다.
경고( 1e500 ); // 무한대
조금 덜 분명할 수도 있지만 꽤 자주 발생하는 것은 정밀도의 상실입니다.
다음과 같은 (거짓!) 동일성 테스트를 고려해보세요.
경고( 0.1 + 0.2 == 0.3 ); // 거짓
맞습니다. 0.1
과 0.2
의 합이 0.3
인지 확인하면 false
가 됩니다.
이상한! 0.3
아니면 무엇입니까?
경고( 0.1 + 0.2 ); // 0.30000000000000004
아야! 귀하가 전자 쇼핑 사이트를 만들고 있는데 방문자가 $0.10
와 $0.20
의 상품을 장바구니에 담는다고 가정해 보십시오. 총 주문 금액은 $0.30000000000000004
입니다. 그러면 누구라도 놀랄 것입니다.
그런데 왜 이런 일이 발생합니까?
숫자는 1과 0의 비트 시퀀스인 이진 형식으로 메모리에 저장됩니다. 그러나 10진수 체계에서 단순해 보이는 0.1
, 0.2
와 같은 분수는 실제로 이진수 형식에서는 끝이 없는 분수입니다.
경고(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 경고(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 경고((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101
0.1
무엇입니까? 1을 10분의 1로 나눈 값, 즉 1/10
입니다. 십진수 체계에서는 이러한 숫자를 쉽게 표현할 수 있습니다. 1/3과 비교해보세요: 1/3
. 이는 무한 분수 0.33333(3)
이 됩니다.
따라서 십진법에서는 10
의 거듭제곱으로 나누기가 잘 작동하지만 3
으로 나누기는 그렇지 않습니다. 같은 이유로 이진수 시스템에서는 2
의 거듭제곱으로 나누는 것이 보장되지만 1/10
끝없는 이진 분수가 됩니다.
1/3을 소수로 저장할 수 없는 것처럼 이진법을 사용하여 정확히 0.1 또는 정확히 0.2를 저장할 방법이 없습니다.
숫자 형식 IEEE-754는 가능한 가장 가까운 숫자로 반올림하여 이 문제를 해결합니다. 이러한 반올림 규칙은 일반적으로 "작은 정밀도 손실"을 볼 수 없지만 존재합니다.
우리는 이것을 실제로 볼 수 있습니다:
경고( 0.1.toFixed(20) ); // 0.10000000000000000555
그리고 두 숫자를 더하면 "정밀도 손실"이 합산됩니다.
이것이 0.1 + 0.2
정확히 0.3
아닌 이유입니다.
자바스크립트 뿐만 아니라
다른 많은 프로그래밍 언어에도 동일한 문제가 존재합니다.
PHP, Java, C, Perl 및 Ruby는 동일한 숫자 형식을 기반으로 하기 때문에 정확히 동일한 결과를 제공합니다.
문제를 해결할 수 있나요? 물론, 가장 신뢰할 수 있는 방법은 toFixed(n) 메서드를 사용하여 결과를 반올림하는 것입니다.
합계 = 0.1 + 0.2로 설정합니다. 경고(sum.toFixed(2) ); // "0.30"
toFixed
항상 문자열을 반환한다는 점에 유의하세요. 소수점 이하 2자리가 있는지 확인합니다. 전자 쇼핑을 하고 $0.30
표시해야 하는 경우 실제로 편리합니다. 다른 경우에는 단항 더하기를 사용하여 숫자로 강제 변환할 수 있습니다.
합계 = 0.1 + 0.2로 설정합니다. 경고( +sum.toFixed(2) ); // 0.3
또한 일시적으로 숫자에 100(또는 더 큰 숫자)을 곱하여 정수로 바꾸고 수학을 수행한 다음 다시 나눌 수도 있습니다. 그런 다음 정수로 수학을 수행하면 오류가 다소 감소하지만 나누기에서는 여전히 오류가 발생합니다.
경고((0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 경고((0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
따라서 곱하기/나누기 방식은 오류를 줄이지만 완전히 제거하지는 않습니다.
때때로 우리는 분수를 전혀 피하려고 시도할 수도 있습니다. 상점과 거래하는 경우 달러 대신 센트로 가격을 저장할 수 있습니다. 그런데 30% 할인을 적용하면 어떨까요? 실제로 분수를 완전히 회피하는 것은 거의 불가능합니다. 필요할 때 "꼬리"를 자르려면 둥글게 만드십시오.
재미있는 것은
다음을 실행해 보세요.
// 안녕하세요! 나는 저절로 늘어나는 숫자입니다! 경고( 9999999999999999 ); // 10000000000000000을 표시합니다.
이는 정밀도 손실이라는 동일한 문제로 인해 어려움을 겪습니다. 숫자에는 64비트가 있고 그 중 52개는 숫자를 저장하는 데 사용할 수 있지만 그것만으로는 충분하지 않습니다. 따라서 최하위 숫자가 사라집니다.
JavaScript는 이러한 이벤트에서 오류를 발생시키지 않습니다. 숫자를 원하는 형식에 맞추는 것이 최선이지만 안타깝게도 이 형식은 충분하지 않습니다.
0이 두 개
숫자 내부 표현의 또 다른 재미있는 결과는 0
과 -0
이라는 두 개의 0이 존재한다는 것입니다.
부호는 단일 비트로 표현되므로 0을 포함한 모든 숫자에 대해 설정되거나 설정되지 않을 수 있기 때문입니다.
대부분의 경우 연산자는 이를 동일하게 취급하는 데 적합하므로 구별이 눈에 띄지 않습니다.
이 두 가지 특수 숫자 값을 기억하시나요?
Infinity
(및 -Infinity
)는 무엇보다 큰(작은) 특수 숫자 값입니다.
NaN
오류를 나타냅니다.
number
유형에 속하지만 "일반적인" 숫자가 아니므로 이를 확인하는 특수 함수가 있습니다.
isNaN(value)
은 인수를 숫자로 변환한 다음 NaN
인지 테스트합니다.
경고( isNaN(NaN) ); // 진실 경고( isNaN("str") ); // 진실
그런데 이 기능이 꼭 필요한가요? === NaN
비교를 사용할 수는 없나요? 불행히도 그렇지 않습니다. NaN
값은 자신을 포함하여 어떤 것과도 같지 않다는 점에서 고유합니다.
경고( NaN === NaN ); // 거짓
isFinite(value)
인수를 숫자로 변환하고 NaN/Infinity/-Infinity
아닌 일반 숫자인 경우 true
반환합니다.
경고( isFinite("15") ); // 진실 경고( isFinite("str") ); // false, 특수 값: NaN 때문입니다. 경고( isFinite(Infinity) ); // false, 특별한 값이 있기 때문에: 무한대
때때로 isFinite
는 문자열 값이 일반 숫자인지 확인하는 데 사용됩니다.
let num = +prompt("숫자를 입력하세요", ''); // Infinity, -Infinity 또는 숫자를 입력하지 않는 한 true가 됩니다. 경고( isFinite(num) );
isFinite
포함한 모든 숫자 함수에서 비어 있거나 공백만 있는 문자열은 0
으로 처리됩니다.
Number.isNaN
및 Number.isFinite
Number.isNaN 및 Number.isFinite 메서드는 isNaN
및 isFinite
함수보다 "엄격한" 버전입니다. 인수를 숫자로 자동 변환하지 않지만 대신 number
유형에 속하는지 확인합니다.
Number.isNaN(value)
은 인수가 number
유형에 속하고 NaN
인 경우 true
반환합니다. 다른 경우에는 false
반환합니다.
경고( Number.isNaN(NaN) ); // 진실 경고( Number.isNaN("str" / 2) ); // 진실 // 차이점에 유의하세요. 경고( Number.isNaN("str") ); // false, "str"은 숫자 유형이 아닌 문자열 유형에 속하기 때문입니다. 경고( isNaN("str") ); // true, isNaN은 문자열 "str"을 숫자로 변환하고 이 변환의 결과로 NaN을 가져오기 때문입니다.
Number.isFinite(value)
인수가 number
유형에 속하고 NaN/Infinity/-Infinity
아닌 경우 true
반환합니다. 다른 경우에는 false
반환합니다.
경고( Number.isFinite(123) ); // 진실 경고( Number.isFinite(Infinity) ); // 거짓 경고( Number.isFinite(2 / 0) ); // 거짓 // 차이점에 유의하세요. 경고( Number.isFinite("123") ); // false, "123"은 숫자 유형이 아닌 문자열 유형에 속하기 때문입니다. 경고( isFinite("123") ); // true, isFinite가 문자열 "123"을 숫자 123으로 변환하기 때문입니다.
어떤 면에서 Number.isNaN
및 Number.isFinite
는 isNaN
및 isFinite
함수보다 더 간단하고 간단합니다. 그러나 실제로는 isNaN
및 isFinite
작성 시간이 더 짧기 때문에 주로 사용됩니다.
Object.is
와의 비교
===
와 같은 값을 비교하는 특별한 내장 메서드 Object.is
있지만 두 가지 극단적인 경우에 더 안정적입니다.
NaN
과 함께 작동합니다: Object.is(NaN, NaN) === true
, 좋은 것입니다.
값 0
과 -0
다릅니다. Object.is(0, -0) === false
. 기술적으로는 정확합니다. 내부적으로 숫자에는 다른 모든 비트가 0이더라도 다를 수 있는 부호 비트가 있기 때문입니다.
다른 모든 경우에는 Object.is(a, b)
a === b
와 동일합니다.
JavaScript 사양에서 자주 사용되는 Object.is
여기서 언급합니다. 내부 알고리즘이 두 값이 정확히 동일한지 비교해야 하는 경우 Object.is
(내부적으로 SameValue라고 함)를 사용합니다.
더하기 +
또는 Number()
사용한 숫자 변환은 엄격합니다. 값이 정확히 숫자가 아니면 실패합니다.
경고( +"100px" ); // NaN
유일한 예외는 문자열의 시작이나 끝 부분에 있는 공백입니다. 공백은 무시됩니다.
하지만 실제 생활에서는 CSS의 "100px"
또는 "12pt"
와 같이 단위 값을 갖는 경우가 많습니다. 또한 많은 국가에서 금액 뒤에 통화 기호가 표시되므로 "19€"
가 있고 여기서 숫자 값을 추출하려고 합니다.
이것이 바로 parseInt
와 parseFloat
의 목적입니다.
그들은 읽을 수 없을 때까지 문자열에서 숫자를 "읽습니다". 오류가 있는 경우 수집된 번호가 반환됩니다. parseInt
함수는 정수를 반환하는 반면, parseFloat
부동 소수점 숫자를 반환합니다.
경고(parseInt('100px') ); // 100 경고(parseFloat('12.5em') ); // 12.5 경고(parseInt('12.3')); // 12, 정수 부분만 반환됨 경고(parseFloat('12.3.4') ); // 12.3, 두 번째 지점에서 읽기가 중지됩니다.
parseInt/parseFloat
NaN
반환하는 상황이 있습니다. 숫자를 읽을 수 없을 때 발생합니다.
경고(parseInt('a123') ); // NaN, 첫 번째 기호는 프로세스를 중지합니다.
parseInt(str, radix)
의 두 번째 인수
parseInt()
함수에는 선택적인 두 번째 매개변수가 있습니다. 이는 숫자 체계의 기수를 지정하므로, parseInt
16진수, 2진수 등의 문자열도 구문 분석할 수 있습니다.
경고(parseInt('0xff', 16) ); // 255 경고(parseInt('ff', 16) ); // 255, 0x가 없어도 작동합니다. 경고(parseInt('2n9c', 36) ); // 123456
JavaScript에는 수학 함수 및 상수의 작은 라이브러리가 포함된 내장 Math 개체가 있습니다.
몇 가지 예:
Math.random()
0에서 1(1은 포함하지 않음) 사이의 난수를 반환합니다.
경고( Math.random() ); // 0.1234567894322 경고( Math.random() ); // 0.5435252343232 경고( Math.random() ); // ... (임의의 숫자)
Math.max(a, b, c...)
및 Math.min(a, b, c...)
임의 개수의 인수에서 가장 큰 것과 가장 작은 것을 반환합니다.
경고( Math.max(3, 5, -10, 0, 1) ); // 5 경고( Math.min(1, 2) ); // 1
Math.pow(n, power)
주어진 n
반환합니다.
경고( Math.pow(2, 10) ); // 2의 거듭제곱 10 = 1024
Math 개체에 대한 문서에서 찾을 수 있는 삼각법을 포함하여 Math
개체에는 더 많은 함수와 상수가 있습니다.
0이 많은 숫자를 쓰려면:
숫자에 0이 포함된 "e"
추가합니다. 예: 123e6
6개의 0이 있는 123
과 동일합니다 123000000
.
"e"
뒤에 음수가 있으면 해당 숫자는 주어진 0과 함께 1로 나누어집니다. 예를 들어 123e-6
0.000123
( 123
2300만분의 1)을 의미합니다.
다른 숫자 시스템의 경우:
16진수( 0x
), 8진수( 0o
) 및 2진수( 0b
) 시스템으로 숫자를 직접 쓸 수 있습니다.
parseInt(str, base)
문자열 str
주어진 base
, 2 ≤ base ≤ 36
사용하여 수 체계의 정수로 구문 분석합니다.
num.toString(base)
주어진 base
사용하여 숫자를 기수 체계의 문자열로 변환합니다.
일반 숫자 테스트의 경우:
isNaN(value)
은 인수를 숫자로 변환한 다음 NaN
인지 테스트합니다.
Number.isNaN(value)
은 인수가 number
유형에 속하는지 확인하고, 그렇다면 NaN
인지 테스트합니다.
isFinite(value)
인수를 숫자로 변환한 다음 NaN/Infinity/-Infinity
아닌지 테스트합니다.
Number.isFinite(value)
인수가 number
유형에 속하는지 확인하고, 그렇다면 NaN/Infinity/-Infinity
가 아닌지 테스트합니다.
12pt
및 100px
와 같은 값을 숫자로 변환하려면 다음을 수행하세요.
문자열에서 숫자를 읽은 다음 오류가 발생하기 전에 읽을 수 있었던 값을 반환하는 "소프트" 변환에는 parseInt/parseFloat
사용하세요.
분수의 경우:
Math.floor
, Math.ceil
, Math.trunc
, Math.round
또는 num.toFixed(precision)
사용하여 반올림합니다.
분수로 작업할 때는 정밀도가 손실된다는 점을 기억하세요.
더 많은 수학 함수:
필요할 때 Math 객체를 참조하세요. 도서관은 매우 작지만 기본적인 요구 사항을 충족할 수 있습니다.
중요도: 5
방문자에게 두 개의 숫자를 입력하라는 메시지를 표시한 다음 그 합계를 표시하는 스크립트를 만듭니다.
데모 실행
추신: 유형에 문제가 있습니다.
let a = +prompt("첫번째 숫자는요?", ""); let b = +prompt("두 번째 숫자는요?", ""); 경고( a + b );
prompt
앞에 단항 더하기 +
있음을 참고하세요. 값을 즉시 숫자로 변환합니다.
그렇지 않으면 a
와 b
문자열이 되며, 그 합은 연결이 됩니다. 즉, "1" + "2" = "12"
입니다.
중요도: 4
Math.round
및 toFixed
문서에 따르면 둘 다 가장 가까운 숫자로 반올림됩니다. 0..4
아래쪽으로, 5..9
위쪽으로 나타납니다.
예를 들어:
경고( 1.35.toFixed(1) ); // 1.4
아래의 유사한 예에서 6.35
6.3
가 아닌 6.4
으로 반올림되는 이유는 무엇입니까?
경고( 6.35.toFixed(1) ); // 6.3
6.35
올바른 방법으로 반올림하는 방법은 무엇입니까?
내부적으로 소수 6.35
무한 이진수입니다. 이러한 경우 항상 그렇듯이 정밀도가 손실된 상태로 저장됩니다.
보자:
경고( 6.35.toFixed(20) ); // 6.34999999999999964473
정밀도 손실로 인해 숫자가 증가하거나 감소할 수 있습니다. 이 특별한 경우에는 숫자가 약간 작아지므로 반올림됩니다.
그리고 1.35
무엇입니까?
경고( 1.35.toFixed(20) ); // 1.35000000000000008882
여기서는 정밀도 손실로 인해 숫자가 조금 더 커지므로 반올림되었습니다.
6.35
올바른 방향으로 반올림하려면 어떻게 문제를 해결할 수 있습니까?
반올림하기 전에 정수에 더 가까워져야 합니다.
경고((6.35 * 10).toFixed(20) ); // 63.50000000000000000000
63.5
에는 정밀도 손실이 전혀 없습니다. 그 이유는 소수 부분 0.5
실제로 1/2
이기 때문입니다. 2
의 거듭제곱으로 나눈 분수는 이진법으로 정확하게 표현됩니다. 이제 반올림할 수 있습니다.
경고( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(반올림) -> 6.4
중요도: 5
방문자가 유효한 숫자 값을 입력할 때까지 숫자를 묻는 함수 readNumber
만듭니다.
결과 값은 숫자로 반환되어야 합니다.
방문자는 빈 줄을 입력하거나 "취소"를 눌러 프로세스를 중지할 수도 있습니다. 이 경우 함수는 null
반환해야 합니다.
데모 실행
테스트를 통해 샌드박스를 엽니다.
함수 readNumber() { 숫자를 보자; 하다 { num = 프롬프트("숫자를 입력하시겠습니까?", 0); } while ( !isFinite(num) ); if (num === null || num === '') return null; +숫자 반환; } Alert(`읽기: ${readNumber()}`);
null
/빈 줄을 처리해야 하기 때문에 솔루션이 조금 더 복잡할 수 있습니다.
따라서 실제로 "정규 숫자"가 될 때까지 입력을 받아들입니다. null
(취소) 및 빈 줄도 모두 해당 조건에 적합합니다. 숫자 형식에서는 0
이기 때문입니다.
중지한 후에는 null
및 빈 줄을 숫자로 변환하면 0
반환되므로 특별히 처리해야 합니다( null
반환).
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 4
이 루프는 무한합니다. 결코 끝나지 않습니다. 왜?
내가 = 0이라고 하자; 동안 (i != 10) { 나는 += 0.2; }
그 이유는 i
결코 10
과 같을 수 없기 때문입니다.
이를 실행하여 i
의 실제 값을 확인합니다.
내가 = 0이라고 하자; 동안 (i < 11) { 나는 += 0.2; if (i > 9.8 && i < 10.2) 경고( i ); }
그들 중 어느 것도 정확히 10
아닙니다.
이러한 일은 0.2
와 같은 분수를 추가할 때 정밀도 손실로 인해 발생합니다.
결론: 소수점 이하 자릿수를 다룰 때 동등성 검사를 피하세요.
중요도: 2
내장 함수 Math.random()
0
에서 1
까지의 임의의 값을 생성합니다( 1
포함하지 않음).
min
부터 max
까지( max
포함하지 않음) 임의의 부동 소수점 숫자를 생성하는 함수 random(min, max)
를 작성하세요.
작업의 예:
경고(random(1, 5) ); // 1.2345623452 경고(random(1, 5) ); // 3.7894332423 경고(random(1, 5) ); // 4.3435234525
간격 0…1의 모든 값을 min
에서 max
까지의 값으로 "매핑"해야 합니다.
이는 두 단계로 수행될 수 있습니다.
0…1의 임의의 숫자에 max-min
곱하면 가능한 값의 간격이 0..1
에서 0..max-min
으로 증가합니다.
이제 min
추가하면 가능한 간격은 min
에서 max
가 됩니다.
기능:
함수 랜덤(최소, 최대) { return min + Math.random() * (최대 - 최소); } 경고(random(1, 5) ); 경고(random(1, 5) ); 경고(random(1, 5) );
중요도: 2
min
과 max
를 가능한 값으로 포함하여 min
에서 max
까지 임의 의 정수를 생성하는 함수 randomInteger(min, max)
를 만듭니다.
min..max
구간의 모든 숫자는 동일한 확률로 나타나야 합니다.
작업의 예:
경고(randomInteger(1, 5) ); // 1 경고(randomInteger(1, 5) ); // 3 경고(randomInteger(1, 5) ); // 5
이전 작업의 솔루션을 기반으로 사용할 수 있습니다.
가장 간단하지만 잘못된 해결책은 min
에서 max
까지 값을 생성하고 반올림하는 것입니다.
함수randomInteger(최소, 최대) { rand = min + Math.random() * (최대 - 최소); return Math.round(rand); } 경고(randomInteger(1, 3) );
기능은 작동하지만 올바르지 않습니다. min
과 max
값을 얻을 확률은 다른 값보다 2배 낮습니다.
위의 예제를 여러번 실행해보면 2
가장 많이 나타나는 것을 쉽게 알 수 있습니다.
이는 Math.round()
가 간격 1..3
에서 난수를 가져와 다음과 같이 반올림하기 때문에 발생합니다.
1 ...에서 1.4999999999 사이의 값은 1이 됩니다. 1.5 ...에서 2.4999999999 사이의 값은 2가 됩니다. 2.5 ...에서 2.9999999999 사이의 값은 3이 됩니다.
이제 우리는 1
2
보다 두 배 적은 값을 얻는다는 것을 분명히 알 수 있습니다. 그리고 3
과 동일합니다.
작업에 대한 올바른 솔루션이 많이 있습니다. 그 중 하나는 간격 경계를 조정하는 것입니다. 동일한 간격을 보장하기 위해 0.5 to 3.5
의 값을 생성하여 가장자리에 필요한 확률을 추가할 수 있습니다.
함수randomInteger(최소, 최대) { // 이제 rand는 (min-0.5)에서 (max+0.5)까지입니다. rand = min - 0.5 + Math.random() * (max - min + 1); return Math.round(rand); } 경고(randomInteger(1, 3) );
또 다른 방법은 min
에서 max+1
까지의 난수에 대해 Math.floor
사용하는 것입니다.
함수randomInteger(최소, 최대) { // 여기서 rand는 min부터 (max+1)까지입니다. rand = min + Math.random() * (최대 + 1 - 최소); return Math.floor(rand); } 경고(randomInteger(1, 3) );
이제 모든 간격이 다음과 같이 매핑됩니다.
1 ...에서 1.9999999999 사이의 값은 1이 됩니다. 2 ...에서 2.9999999999까지의 값은 2가 됩니다. 3 ...에서 3.9999999999까지의 값은 3이 됩니다.
모든 구간의 길이가 동일하므로 최종 분포가 균일해집니다.