@@ -2,7 +2,6 @@ import { expect } from 'chai';
2
2
import { BSON , Double } from '../register-bson' ;
3
3
4
4
import { BSON_DATA_NUMBER , BSON_DATA_INT } from '../../src/constants' ;
5
- import { inspect } from 'node:util' ;
6
5
7
6
describe ( 'BSON Double Precision' , function ( ) {
8
7
context ( 'class Double' , function ( ) {
@@ -40,24 +39,158 @@ describe('BSON Double Precision', function () {
40
39
41
40
describe ( '.toExtendedJSON()' , ( ) => {
42
41
const tests = [
43
- { input : new Double ( 0 ) , output : { $numberDouble : '0.0' } } ,
44
- { input : new Double ( - 0 ) , output : { $numberDouble : '-0.0' } } ,
45
- { input : new Double ( 3 ) , output : { $numberDouble : '3.0' } } ,
46
- { input : new Double ( - 3 ) , output : { $numberDouble : '-3.0' } } ,
47
- { input : new Double ( 3.4 ) , output : { $numberDouble : '3.4' } } ,
48
- { input : new Double ( Number . EPSILON ) , output : { $numberDouble : '2.220446049250313e-16' } } ,
49
- { input : new Double ( 12345e7 ) , output : { $numberDouble : '123450000000.0' } } ,
50
- { input : new Double ( 12345e-1 ) , output : { $numberDouble : '1234.5' } } ,
51
- { input : new Double ( - 12345e-1 ) , output : { $numberDouble : '-1234.5' } } ,
52
- { input : new Double ( Infinity ) , output : { $numberDouble : 'Infinity' } } ,
53
- { input : new Double ( - Infinity ) , output : { $numberDouble : '-Infinity' } } ,
54
- { input : new Double ( NaN ) , output : { $numberDouble : 'NaN' } }
42
+ {
43
+ title : 'returns "0.0" when input is a number 0' ,
44
+ input : 0 ,
45
+ output : { $numberDouble : '0.0' }
46
+ } ,
47
+ {
48
+ title : 'returns "-0.0" when input is a number -0' ,
49
+ input : - 0 ,
50
+ output : { $numberDouble : '-0.0' }
51
+ } ,
52
+ {
53
+ title : 'returns "0.0" when input is a string "-0.0"' ,
54
+ input : '-0.0' ,
55
+ output : { $numberDouble : '-0.0' }
56
+ } ,
57
+ {
58
+ title : 'returns "3.0" when input is a number 3' ,
59
+ input : 3 ,
60
+ output : { $numberDouble : '3.0' }
61
+ } ,
62
+ {
63
+ title : 'returns "-3.0" when input is a number -3' ,
64
+ input : - 3 ,
65
+ output : { $numberDouble : '-3.0' }
66
+ } ,
67
+ {
68
+ title : 'returns "3.4" when input is a number 3.4' ,
69
+ input : 3.4 ,
70
+ output : { $numberDouble : '3.4' }
71
+ } ,
72
+ {
73
+ title : 'returns "2.220446049250313e-16" when input is Number.EPSILON' ,
74
+ input : Number . EPSILON ,
75
+ output : { $numberDouble : '2.220446049250313e-16' }
76
+ } ,
77
+ {
78
+ title : 'returns "123450000000.0" when input is a number 12345e7' ,
79
+ input : 12345e7 ,
80
+ output : { $numberDouble : '123450000000.0' }
81
+ } ,
82
+ {
83
+ title : 'returns "1234.5" when input is a number 12345e-1' ,
84
+ input : 12345e-1 ,
85
+ output : { $numberDouble : '1234.5' }
86
+ } ,
87
+ {
88
+ title : 'returns "-1234.5" when input is a number -12345e-1' ,
89
+ input : - 12345e-1 ,
90
+ output : { $numberDouble : '-1234.5' }
91
+ } ,
92
+ {
93
+ title : 'returns "Infinity" when input is a number Infinity' ,
94
+ input : Infinity ,
95
+ output : { $numberDouble : 'Infinity' }
96
+ } ,
97
+ {
98
+ title : 'returns "-Infinity" when input is a number -Infinity' ,
99
+ input : - Infinity ,
100
+ output : { $numberDouble : '-Infinity' }
101
+ } ,
102
+ {
103
+ title : 'returns "NaN" when input is a number NaN' ,
104
+ input : NaN ,
105
+ output : { $numberDouble : 'NaN' }
106
+ } ,
107
+ {
108
+ title : 'returns "1.7976931348623157e+308" when input is a number Number.MAX_VALUE' ,
109
+ input : Number . MAX_VALUE ,
110
+ output : { $numberDouble : '1.7976931348623157e+308' }
111
+ } ,
112
+ {
113
+ title : 'returns "5e-324" when input is a number Number.MIN_VALUE' ,
114
+ input : Number . MIN_VALUE ,
115
+ output : { $numberDouble : '5e-324' }
116
+ } ,
117
+ {
118
+ title : 'returns "-1.7976931348623157e+308" when input is a number -Number.MAX_VALUE' ,
119
+ input : - Number . MAX_VALUE ,
120
+ output : { $numberDouble : '-1.7976931348623157e+308' }
121
+ } ,
122
+ {
123
+ title : 'returns "-5e-324" when input is a number -Number.MIN_VALUE' ,
124
+ input : - Number . MIN_VALUE ,
125
+ output : { $numberDouble : '-5e-324' }
126
+ } ,
127
+ {
128
+ // Reference: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_math.html
129
+ // min positive normal number
130
+ title :
131
+ 'returns "2.2250738585072014e-308" when input is a number the minimum positive normal value' ,
132
+ input : '2.2250738585072014e-308' ,
133
+ output : { $numberDouble : '2.2250738585072014e-308' }
134
+ } ,
135
+ {
136
+ // max subnormal number
137
+ title :
138
+ 'returns "2.225073858507201e-308" when input is a number the maximum positive subnormal value' ,
139
+ input : '2.225073858507201e-308' ,
140
+ output : { $numberDouble : '2.225073858507201e-308' }
141
+ } ,
142
+ {
143
+ // min positive subnormal number (NOTE: JS does not output same input string, but numeric values are equal)
144
+ title : 'returns "5e-324" when input is a number the minimum positive subnormal value' ,
145
+ input : '4.9406564584124654e-324' ,
146
+ output : { $numberDouble : '5e-324' }
147
+ } ,
148
+ {
149
+ // https://262.ecma-international.org/13.0/#sec-number.prototype.tofixed
150
+ // Note: calling toString on this integer returns 1000000000000000100, so toFixed is more precise
151
+ // This test asserts we do not change _current_ behavior, however preserving this value is not
152
+ // something that is possible in BSON, if a future version of this library were to emit
153
+ // "1000000000000000100.0" instead, it would not be incorrect from a BSON/MongoDB/Double precision perspective,
154
+ // it would just constrain the string output to what is possible with 8 bytes of floating point precision
155
+ title :
156
+ 'returns "1000000000000000128.0" when input is an int-like number beyond 8-byte double precision' ,
157
+ input : '1000000000000000128' ,
158
+ output : { $numberDouble : '1000000000000000128.0' }
159
+ }
55
160
] ;
56
161
57
- for ( const { input, output } of tests ) {
58
- const title = `returns ${ inspect ( output ) } when Double is ${ input } ` ;
162
+ for ( const test of tests ) {
163
+ const input = test . input ;
164
+ const output = test . output ;
165
+ const title = test . title ;
59
166
it ( title , ( ) => {
60
- expect ( output ) . to . deep . equal ( input . toExtendedJSON ( { relaxed : false } ) ) ;
167
+ const inputAsDouble = new Double ( input ) ;
168
+ expect ( inputAsDouble . toExtendedJSON ( { relaxed : false } ) ) . to . deep . equal ( output ) ;
169
+ if ( ! Number . isNaN ( inputAsDouble . value ) ) {
170
+ expect ( Number ( inputAsDouble . toExtendedJSON ( { relaxed : false } ) . $numberDouble ) ) . to . equal (
171
+ inputAsDouble . value
172
+ ) ;
173
+ }
174
+ } ) ;
175
+
176
+ it ( `preserves the byte wise value of ${ input } (${ typeof input } ) after stringification` , ( ) => {
177
+ // Asserts the same bytes can be reconstructed from the generated string,
178
+ // sometimes the string changes "4.9406564584124654e-324" -> "5e-324"
179
+ // but both represent the same ieee754 double bytes
180
+ const ejsonDoubleString = new Double ( input ) . toExtendedJSON ( ) . $numberDouble ;
181
+ const bytesFromInput = ( ( ) => {
182
+ const b = Buffer . alloc ( 8 ) ;
183
+ b . writeDoubleBE ( Number ( input ) ) ;
184
+ return b . toString ( 'hex' ) ;
185
+ } ) ( ) ;
186
+
187
+ const bytesFromOutput = ( ( ) => {
188
+ const b = Buffer . alloc ( 8 ) ;
189
+ b . writeDoubleBE ( Number ( ejsonDoubleString ) ) ;
190
+ return b . toString ( 'hex' ) ;
191
+ } ) ( ) ;
192
+
193
+ expect ( bytesFromOutput ) . to . equal ( bytesFromInput ) ;
61
194
} ) ;
62
195
}
63
196
} ) ;
0 commit comments