blob: cd264632101003a1f51e9a191a371549f4aa482a [file] [log] [blame]
Laurence Lundblade68a13352018-09-23 02:19:54 -07001/*==============================================================================
2 Copyright 2018 Laurence Lundblade
3
4 Permission is hereby granted, free of charge, to any person obtaining
5 a copy of this software and associated documentation files (the
6 "Software"), to deal in the Software without restriction, including
7 without limitation the rights to use, copy, modify, merge, publish,
8 distribute, sublicense, and/or sell copies of the Software, and to
9 permit persons to whom the Software is furnished to do so, subject to
10 the following conditions:
11
12 The above copyright notice and this permission notice shall be included
13 in all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23
24 (This is the MIT license)
25 ==============================================================================*/
26//
27// half_precision_test.c
28// QCBOR
29//
30// Created by Laurence Lundblade on 9/19/18.
31// Copyright © 2018 Laurence Lundblade. All rights reserved.
32//
33
34#include "half_precision_test.h"
35#include "qcbor.h"
Laurence Lundbladed711fb22018-09-26 14:35:22 -070036#include "half_to_double_from_rfc7049.h"
37#include <math.h> // For INFINITY and NAN and isnan()
Laurence Lundblade68a13352018-09-23 02:19:54 -070038
39static const uint8_t ExpectedHalf[] = {
Laurence Lundblade7d40d812018-09-30 02:44:01 -070040 0xB1,
Laurence Lundblade68a13352018-09-23 02:19:54 -070041 0x64,
42 0x7A, 0x65, 0x72, 0x6F,
43 0xF9, 0x00, 0x00, // 0.000
44 0x6A,
45 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79,
46 0xF9, 0x7C, 0x00, // Infinity
47 0x73,
48 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79,
49 0xF9, 0xFC, 0x00, // -Inifinity
50 0x63,
51 0x4E, 0x61, 0x4E,
52 0xF9, 0x7E, 0x00, // NaN
53 0x63,
54 0x6F, 0x6E, 0x65,
55 0xF9, 0x3C, 0x00, // 1.0
56 0x69,
57 0x6F, 0x6E, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64,
58 0xF9, 0x35, 0x55, // 0.333251953125
59 0x76,
60 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E,
61 0xF9, 0x7B, 0xFF, // 65504.0
62 0x78, 0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E,
63 0xF9, 0x7C, 0x00, // Infinity
64 0x72,
65 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
66 0xF9, 0x00, 0x01, // 0.000000059604
67 0x6F,
68 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
69 0xF9, 0x03, 0xFF, // 0.0000609755516
70 0x71,
71 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
72 0xF9, 0x04, 0x00, // 0.000061988
73 0x70,
74 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65,
75 0xF9, 0x00, 0x00,
76 0x03,
Laurence Lundblade7d40d812018-09-30 02:44:01 -070077 0xF9, 0xC0, 0x00, // -2
78 0x04,
79 0xF9, 0x7E, 0x00, // qNaN
80 0x05,
81 0xF9, 0x7C, 0x01, // sNaN
82 0x06,
83 0xF9, 0x7E, 0x0F, // qNaN with payload 0x0f
84 0x07,
85 0xF9, 0x7C, 0x0F, // sNaN with payload 0x0f
86
Laurence Lundblade68a13352018-09-23 02:19:54 -070087};
88
89
90
91int half_precision_encode_basic()
92{
Laurence Lundblade7d40d812018-09-30 02:44:01 -070093 UsefulBuf_MakeStackUB(EncodedHalfsMem, 250);
Laurence Lundblade68a13352018-09-23 02:19:54 -070094
95 QCBOREncodeContext EC;
96 QCBOREncode_Init(&EC, EncodedHalfsMem);
97 // These are mostly from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
98 QCBOREncode_OpenMap(&EC);
99 QCBOREncode_AddFloatAsHalfToMap(&EC, "zero", 0.00F);
100 QCBOREncode_AddFloatAsHalfToMap(&EC, "infinitity", INFINITY);
101 QCBOREncode_AddFloatAsHalfToMap(&EC, "negative infinitity", -INFINITY);
102 QCBOREncode_AddFloatAsHalfToMap(&EC, "NaN", NAN);
103 QCBOREncode_AddFloatAsHalfToMap(&EC, "one", 1.0F);
104 QCBOREncode_AddFloatAsHalfToMap(&EC, "one third", 0.333251953125F);
105 QCBOREncode_AddFloatAsHalfToMap(&EC, "largest half-precision",65504.0F);
106 // Float 65536.0F is 0x47800000 in hex. It has an exponent of 16, which is larger than 15, the largest half-precision exponent
107 QCBOREncode_AddFloatAsHalfToMap(&EC, "too-large half-precision", 65536.0F);
108 // Should convert to smallest possible half precision which is encodded as 0x00 0x01 or 5.960464477539063e-8
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700109 QCBOREncode_AddFloatAsHalfToMap(&EC, "smallest subnormal", 0.0000000596046448F);
Laurence Lundblade68a13352018-09-23 02:19:54 -0700110 QCBOREncode_AddFloatAsHalfToMap(&EC, "smallest normal", 0.0000610351526F); // in hex single is 0x387fffff, exponent -15, significand 7fffff
111 QCBOREncode_AddFloatAsHalfToMap(&EC, "biggest subnormal", 0.0000610351563F); // in hex single is 0x38800000, exponent -14, significand 0
112 QCBOREncode_AddFloatAsHalfToMap(&EC, "subnormal single", 4e-40F);
113 QCBOREncode_AddFloatAsHalfToMapN(&EC, 3, -2.0F);
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700114 QCBOREncode_AddFloatAsHalfToMapN(&EC, 4, UsefulBufUtil_CopyUint32ToFloat(0x7fc00000L)); // qNaN
115 QCBOREncode_AddFloatAsHalfToMapN(&EC, 5, UsefulBufUtil_CopyUint32ToFloat(0x7f800001L)); // sNaN
116 QCBOREncode_AddFloatAsHalfToMapN(&EC, 6, UsefulBufUtil_CopyUint32ToFloat(0x7fc0f00fL)); // qNaN with payload
117 QCBOREncode_AddFloatAsHalfToMapN(&EC, 7, UsefulBufUtil_CopyUint32ToFloat(0x7f80f00fL)); // sNaN with payload
Laurence Lundblade68a13352018-09-23 02:19:54 -0700118 QCBOREncode_CloseMap(&EC);
119
120 EncodedCBOR EncodedHalfs;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700121 int nReturn = QCBOREncode_Finish2(&EC, &EncodedHalfs);
Laurence Lundblade68a13352018-09-23 02:19:54 -0700122 if(nReturn) {
123 return -1;
124 }
125
126 if(UsefulBuf_Compare(EncodedHalfs.Bytes, UsefulBuf_FromByteArrayLiteral(ExpectedHalf))) {
127 return -3;
128 }
129
130 return 0;
131}
132
133
134int half_precision_decode_basic()
135{
136 UsefulBufC HalfPrecision = UsefulBuf_FromByteArrayLiteral(ExpectedHalf);
137
138 QCBORDecodeContext DC;
139 QCBORDecode_Init(&DC, HalfPrecision, 0);
140
141 QCBORItem Item;
142
143 QCBORDecode_GetNext(&DC, &Item);
144 if(Item.uDataType != QCBOR_TYPE_MAP) {
145 return -1;
146 }
147
148 QCBORDecode_GetNext(&DC, &Item);
149 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0.0F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700150 return -2;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700151 }
152
153 QCBORDecode_GetNext(&DC, &Item);
154 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != INFINITY) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700155 return -3;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700156 }
157
158 QCBORDecode_GetNext(&DC, &Item);
159 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != -INFINITY) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700160 return -4;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700161 }
162
163 QCBORDecode_GetNext(&DC, &Item); // TODO, is this really converting right? It is carrying payload, but this confuses things.
164 if(Item.uDataType != QCBOR_TYPE_FLOAT || !isnan(Item.val.fnum)) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700165 return -5;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700166 }
167
168 QCBORDecode_GetNext(&DC, &Item);
169 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 1.0F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700170 return -6;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700171 }
172
173 QCBORDecode_GetNext(&DC, &Item);
174 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0.333251953125F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700175 return -7;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700176 }
177
178 QCBORDecode_GetNext(&DC, &Item);
179 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 65504.0F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700180 return -8;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700181 }
182
183 QCBORDecode_GetNext(&DC, &Item);
184 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != INFINITY) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700185 return -9;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700186 }
187
188 QCBORDecode_GetNext(&DC, &Item); // TODO: check this
189 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0.0000000596046448F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700190 return -10;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700191 }
192
193 QCBORDecode_GetNext(&DC, &Item); // TODO: check this
194 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0.0000609755516F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700195 return -11;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700196 }
197
198 QCBORDecode_GetNext(&DC, &Item); // TODO check this
199 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0.0000610351563F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700200 return -12;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700201 }
202
203 QCBORDecode_GetNext(&DC, &Item);
204 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != 0) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700205 return -13;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700206 }
207
208 QCBORDecode_GetNext(&DC, &Item);
209 if(Item.uDataType != QCBOR_TYPE_FLOAT || Item.val.fnum != -2.0F) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700210 return -14;
211 }
212
213 QCBORDecode_GetNext(&DC, &Item);
214 if(Item.uDataType != QCBOR_TYPE_FLOAT || UsefulBufUtil_CopyFloatToUint32(Item.val.fnum) != 0x7fc00000L) {
215 return -15;
216 }
217 QCBORDecode_GetNext(&DC, &Item);
218 if(Item.uDataType != QCBOR_TYPE_FLOAT || UsefulBufUtil_CopyFloatToUint32(Item.val.fnum) != 0x7f800001) {
219 return -16;
220 }
221 QCBORDecode_GetNext(&DC, &Item);
222 if(Item.uDataType != QCBOR_TYPE_FLOAT || UsefulBufUtil_CopyFloatToUint32(Item.val.fnum) != 0x7fc0000f) {
223 return -17;
224 }
225 QCBORDecode_GetNext(&DC, &Item);
226 if(Item.uDataType != QCBOR_TYPE_FLOAT || UsefulBufUtil_CopyFloatToUint32(Item.val.fnum) != 0x7f80000f) {
227 return -18;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700228 }
229
230 if(QCBORDecode_Finish(&DC)) {
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700231 return -19;
Laurence Lundblade68a13352018-09-23 02:19:54 -0700232 }
233
234 return 0;
235}
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700236
237
238int half_precision_to_float_transitive_test()
239{
240 for(uint32_t uHalfP = 0; uHalfP < 0xffff; uHalfP += 1) {
241 // Contruct the CBOR for the half-precision float by hand
242 UsefulBuf_MakeStackUB(EncodedCBORMem, 3);
243 UsefulOutBuf UOB;
244 UsefulOutBuf_Init(&UOB, EncodedCBORMem);
245
246 const uint8_t uHalfPrecInitialByte = HALF_PREC_FLOAT + (CBOR_MAJOR_TYPE_SIMPLE << 5); // 0xf9
247 UsefulOutBuf_AppendByte(&UOB, uHalfPrecInitialByte); // The initial byte for a half-precision float
248 UsefulOutBuf_AppendUint16(&UOB, (uint16_t)uHalfP);
249
250
251 // Now parse the hand-constructed CBOR. This will invoke the conversion to a float
252 QCBORDecodeContext DC;
253 QCBORDecode_Init(&DC, UsefulOutBuf_OutUBuf(&UOB), 0);
254
255 QCBORItem Item;
256 QCBORDecode_GetNext(&DC, &Item);
257 if(Item.uDataType != QCBOR_TYPE_FLOAT) {
258 return -1;
259 }
260
261 //printf("%04x QCBOR:%15.15f \n", uHalfP,Item.val.fnum);
262
263
264 // Now generate CBOR with the half-precision value. This will invoke the conversion from float to half
265 UsefulBuf_MakeStackUB(OtherEncodedCBORMem, 5);
266 QCBOREncodeContext EC;
267 QCBOREncode_Init(&EC, OtherEncodedCBORMem);
268 QCBOREncode_AddFloatAsHalf(&EC, Item.val.fnum);
269 EncodedCBOR EnCBOR;
270 QCBOREncode_Finish2(&EC, &EnCBOR); // todo check return code
271
272
273 // Finally parse the CBOR by hand to get at half-precision that was actually encoded.
274 UsefulInputBuf UIB;
275 UsefulInputBuf_Init(&UIB, EnCBOR.Bytes);
276 if(UsefulInputBuf_GetByte(&UIB) != uHalfPrecInitialByte) {
277 return -2;
278 }
279 if(UsefulInputBuf_GetUint16(&UIB) != uHalfP) { // the moment of truth did we get back what we started with?
280 return -3;
281 }
282 }
283
284 return 0;
285}
286
287
288int half_precision_to_float_vs_rfc_test()
289{
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700290 for(uint32_t uHalfP = 0; uHalfP < 0xffff; uHalfP += 60) {
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700291 unsigned char x[2];
292 x[1] = uHalfP & 0xff;
293 x[0] = uHalfP >> 8;
294 double d = decode_half(x);
295
296 // Contruct the CBOR for the half-precision float by hand
297 UsefulBuf_MakeStackUB(__xx, 3);
298 UsefulOutBuf UOB;
299 UsefulOutBuf_Init(&UOB, __xx);
300
301 const uint8_t uHalfPrecInitialByte = HALF_PREC_FLOAT + (CBOR_MAJOR_TYPE_SIMPLE << 5); // 0xf9
302 UsefulOutBuf_AppendByte(&UOB, uHalfPrecInitialByte); // The initial byte for a half-precision float
303 UsefulOutBuf_AppendUint16(&UOB, (uint16_t)uHalfP);
304
305 // Now parse the hand-constructed CBOR. This will invoke the conversion to a float
306 QCBORDecodeContext DC;
307 QCBORDecode_Init(&DC, UsefulOutBuf_OutUBuf(&UOB), 0);
308
309 QCBORItem Item;
310
311 QCBORDecode_GetNext(&DC, &Item);
312 if(Item.uDataType != QCBOR_TYPE_FLOAT) {
313 return -1;
314 }
315
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700316 //printf("%04x QCBOR:%15.15f RFC: %15.15f (%8x)\n", uHalfP,Item.val.fnum, d , UsefulBufUtil_CopyFloatToUint32(d));
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700317
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700318 if(isnan(d)) {
319 // The RFC code uses the native instructions which may or may not
320 // handle sNaN, qNaN and NaN payloads correctly. This test just
321 // makes sure it is a NaN and doesn't worry about the type of NaN
322 if(!isnan(Item.val.fnum)) {
323 return -3;
324 }
325 } else {
326 if(Item.val.fnum != d) {
327 return -2;
328 }
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700329 }
330 }
331 return 0;
332}
333
334
335/*
336 {"zero": 0.0, "negative zero": -0.0, "infinitity": Infinity, "negative infinitity": -Infinity, "NaN": NaN, "one": 1.0, "one third": 0.333251953125, "largest half-precision": 65504.0, "largest half-precision point one": 65504.1, "too-large half-precision": 65536.0, "smallest subnormal": 5.96046448e-8, "smallest normal": 0.00006103515261202119, "biggest subnormal": 0.00006103515625, "subnormal single": 4.00000646641519e-40, 3: -2.0, "large single exp": 2.5521177519070385e+38, "too-large single exp": 5.104235503814077e+38, "biggest single with prec": 16777216.0, "first single with prec loss": 16777217.0, 1: "fin"}
337
338 */
339static const uint8_t sExpectedSmallest[] = {
340 0xB4, 0x64, 0x7A, 0x65, 0x72, 0x6F, 0xF9, 0x00, 0x00, 0x6D, 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7A, 0x65, 0x72, 0x6F, 0xF9, 0x80, 0x00, 0x6A, 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, 0xF9, 0x7C, 0x00, 0x73, 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, 0xF9, 0xFC, 0x00, 0x63, 0x4E, 0x61, 0x4E, 0xF9, 0x7E, 0x00, 0x63, 0x6F, 0x6E, 0x65, 0xF9, 0x3C, 0x00, 0x69, 0x6F, 0x6E, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0xF9, 0x35, 0x55, 0x76, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0xF9, 0x7B, 0xFF, 0x78, 0x20, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x20, 0x6F, 0x6E, 0x65, 0xFB, 0x40, 0xEF, 0xFC, 0x03, 0x33, 0x33, 0x33, 0x33, 0x78, 0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0xFA, 0x47, 0x80, 0x00, 0x00, 0x72, 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xFB, 0x3E, 0x70, 0x00, 0x00, 0x00, 0x1C, 0x5F, 0x68, 0x6F, 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xFA, 0x38, 0x7F, 0xFF, 0xFF, 0x71, 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xF9, 0x04, 0x00, 0x70, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0xFB, 0x37, 0xC1, 0x6C, 0x28, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF9, 0xC0, 0x00, 0x70, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x65, 0x78, 0x70, 0xFA, 0x7F, 0x40, 0x00, 0x00, 0x74, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x65, 0x78, 0x70, 0xFB, 0x47, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x18, 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0xFA, 0x4B, 0x80, 0x00, 0x00, 0x78, 0x1B, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0x20, 0x6C, 0x6F, 0x73, 0x73, 0xFB, 0x41, 0x70, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x63, 0x66, 0x69, 0x6E
341};
342
343
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700344
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700345int double_as_smallest_encode_basic()
346{
347 UsefulBuf_MakeStackUB(EncodedHalfsMem, 420);
348
349 QCBOREncodeContext EC;
350 QCBOREncode_Init(&EC, EncodedHalfsMem);
351 // These are mostly from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
352 QCBOREncode_OpenMap(&EC);
353 // 64 # text(4)
354 // 7A65726F # "zero"
355 // F9 0000 # primitive(0)
356 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "zero", 0.00);
357
358 // 64 # text(4)
359 // 7A65726F # "negative zero"
360 // F9 8000 # primitive(0)
361 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative zero", -0.00);
362
363 // 6A # text(10)
364 // 696E66696E6974697479 # "infinitity"
365 // F9 7C00 # primitive(31744)
366 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "infinitity", INFINITY);
367
368 // 73 # text(19)
369 // 6E6567617469766520696E66696E6974697479 # "negative infinitity"
370 // F9 FC00 # primitive(64512)
371 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative infinitity", -INFINITY);
372
373 // 63 # text(3)
374 // 4E614E # "NaN"
375 // F9 7E00 # primitive(32256)
376 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "NaN", NAN);
377
378 // TODO: test a few NaN variants
379
380 // 63 # text(3)
381 // 6F6E65 # "one"
382 // F9 3C00 # primitive(15360)
383 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one", 1.0);
384
385 // 69 # text(9)
386 // 6F6E65207468697264 # "one third"
387 // F9 3555 # primitive(13653)
388 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one third", 0.333251953125);
389
390 // 76 # text(22)
391 // 6C6172676573742068616C662D707265636973696F6E # "largest half-precision"
392 // F9 7BFF # primitive(31743)
393 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision",65504.0);
394
395 // 76 # text(22)
396 // 6C6172676573742068616C662D707265636973696F6E # "largest half-precision"
397 // F9 7BFF # primitive(31743)
398 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision point one",65504.1);
399
400 // Float 65536.0F is 0x47800000 in hex. It has an exponent of 16, which is larger than 15, the largest half-precision exponent
401 // 78 18 # text(24)
402 // 746F6F2D6C617267652068616C662D707265636973696F6E # "too-large half-precision"
403 // FA 47800000 # primitive(31743)
404 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large half-precision", 65536.0);
405
406 // The smallest possible half-precision subnormal, but digitis are lost converting
407 // to half, so this turns into a double
408 // 72 # text(18)
409 // 736D616C6C657374207375626E6F726D616C # "smallest subnormal"
410 // FB 3E700000001C5F68 # primitive(4499096027744984936)
411 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest subnormal", 0.0000000596046448);
412
413 // The smallest possible half-precision snormal, but digitis are lost converting
414 // to half, so this turns into a single TODO: confirm this is right
415 // 6F # text(15)
416 // 736D616C6C657374206E6F726D616C # "smallest normal"
417 // FA 387FFFFF # primitive(947912703)
418 // in hex single is 0x387fffff, exponent -15, significand 7fffff
419 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest normal", 0.0000610351526F);
420
421 // 71 # text(17)
422 // 62696767657374207375626E6F726D616C # "biggest subnormal"
423 // F9 0400 # primitive(1024)
424 // in hex single is 0x38800000, exponent -14, significand 0
425 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest subnormal", 0.0000610351563F);
426
427 // 70 # text(16)
428 // 7375626E6F726D616C2073696E676C65 # "subnormal single"
429 // FB 37C16C2800000000 # primitive(4017611261645684736)
430 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "subnormal single", 4e-40F);
431
432 // 03 # unsigned(3)
433 // F9 C000 # primitive(49152)
434 QCBOREncode_AddDoubleAsSmallestToMapN(&EC, 3, -2.0);
435
436 // 70 # text(16)
437 // 6C617267652073696E676C6520657870 # "large single exp"
438 // FA 7F400000 # primitive(2134900736)
439 // (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((127LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT);
440 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "large single exp", 2.5521177519070385E+38); // Exponent fits single
441
442 // 74 # text(20)
443 // 746F6F2D6C617267652073696E676C6520657870 # "too-large single exp"
444 // FB 47F8000000000000 # primitive(5185894970917126144)
445 // (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((128LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT);
446 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large single exp", 5.104235503814077E+38); // Exponent too large for single
447
448 // 66 # text(6)
449 // 646664666465 # "dfdfde"
450 // FA 4B800000 # primitive(1266679808)
451 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest single with prec",16777216); // Single with no precision loss
452
453 // 78 18 # text(24)
454 // 626967676573742073696E676C6520776974682070726563 # "biggest single with prec"
455 // FA 4B800000 # primitive(1266679808)
456 QCBOREncode_AddDoubleAsSmallestToMap(&EC, "first single with prec loss",16777217); // Double becuase of precision loss
457
458 // Just a convenient marker when cutting and pasting encoded CBOR
459 QCBOREncode_AddSZStringToMapN(&EC, 1, "fin");
460
461 QCBOREncode_CloseMap(&EC);
462
463 EncodedCBOR EncodedHalfs;
464 int nReturn = QCBOREncode_Finish2(&EC, &EncodedHalfs);
465 if(nReturn) {
466 return -1;
467 }
468
469 if(UsefulBuf_Compare(EncodedHalfs.Bytes, UsefulBuf_FromByteArrayLiteral(sExpectedSmallest))) {
470 return -3;
471 }
472
473 return 0;
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700474};
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700475
476
477
Laurence Lundblade7d40d812018-09-30 02:44:01 -0700478#ifdef NAN_EXPERIMENT
479/*
480 Code for checking what the double to float cast does with
481 NaNs. Not run as part of tests. Keep it around to
482 be able to check various platforms and CPUs.
483 */
484
485#define DOUBLE_NUM_SIGNIFICAND_BITS (52)
486#define DOUBLE_NUM_EXPONENT_BITS (11)
487#define DOUBLE_NUM_SIGN_BITS (1)
488
489#define DOUBLE_SIGNIFICAND_SHIFT (0)
490#define DOUBLE_EXPONENT_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS)
491#define DOUBLE_SIGN_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS + DOUBLE_NUM_EXPONENT_BITS)
492
493#define DOUBLE_SIGNIFICAND_MASK (0xfffffffffffffULL) // The lower 52 bits
494#define DOUBLE_EXPONENT_MASK (0x7ffULL << DOUBLE_EXPONENT_SHIFT) // 11 bits of exponent
495#define DOUBLE_SIGN_MASK (0x01ULL << DOUBLE_SIGN_SHIFT) // 1 bit of sign
496#define DOUBLE_QUIET_NAN_BIT (0x01ULL << (DOUBLE_NUM_SIGNIFICAND_BITS-1))
497
498
499static int NaNExperiments() {
500 double dqNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT);
501 double dsNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | 0x01);
502 double dqNaNPayload = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT | 0xf00f);
503
504 float f1 = (float)dqNaN;
505 float f2 = (float)dsNaN;
506 float f3 = (float)dqNaNPayload;
507
508
509 uint32_t uqNaN = UsefulBufUtil_CopyFloatToUint32((float)dqNaN);
510 uint32_t usNaN = UsefulBufUtil_CopyFloatToUint32((float)dsNaN);
511 uint32_t uqNaNPayload = UsefulBufUtil_CopyFloatToUint32((float)dqNaNPayload);
512
513 // Result of this on x86 is that every NaN is a qNaN. The intel
514 // CVTSD2SS instruction ignores the NaN payload and even converts
515 // a sNaN to a qNaN.
516
517 return 0;
518}
519#endif
520
521
Laurence Lundbladed711fb22018-09-26 14:35:22 -0700522