liuxiaolong
2019-05-09 0d1d88cdb668e75ea8609417ac18ae19947e9525
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
 
/**
 * echarts 值轴分段刻度计算方法
 *
 * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
 * @author xieshiwei (谢世威, i6ma@i6ma.com)
 *
 */
 
 
/**
 * 最值、跨度、步长取近似值
 * 注意:不适用于高精度需求,或者很多位有效数字的情况!!!
 * @function    smartSteps
 * @param       {Number}    min             最小值
 * @param       {Number}    max             最大值
 * @param       {Number}    [section]       段数只能是 [0, 99] 的整数,段数为 0 或者不指定段数时,将自动调整段数
 * @param       {Object}    [opts]          其它扩展参数
 * @param       {Array}     opts.steps      自定义步长备选值,如 [10, 12, 15, 20, 25, 30, 40, 50, 60, 80] ,但必须 => [10, 99]
 * @return      {Object}    {min: 新最小值, max: 新最大值, secs: 分段数, step: 每段长, fix: 小数保留位数, pnts: [分段结果]}
 */
define(function() {
 
 
 
var mySteps     = [10, 20, 25, 50];
var mySections  = [4, 5, 6];
 
var custOpts;
var custSteps;
var custSecs;
var minLocked;
var maxLocked;
 
var MT          = Math;
var MATH_ROUND  = MT.round;
var MATH_FLOOR  = MT.floor;
var MATH_CEIL   = MT.ceil;
var MATH_ABS    = MT.abs;
 
 
function MATH_LOG(n) {return MT.log(MATH_ABS(n)) / MT.LN10;}
function MATH_POW(n) {return MT.pow(10, n);}
function MATH_ISINT(n) {return n === MATH_FLOOR(n);}
 
 
function smartSteps(min, max, section, opts) {
    // 拿公共变量来接收 opts.steps 这个参数,就不用带着参数层层传递了,注意在函数的最终出口处释放这个值
    custOpts    = opts || {};
    custSteps   = custOpts.steps || mySteps;
    custSecs    = custOpts.secs || mySections;
    section     = MATH_ROUND(+section || 0) % 99;           // 段数限定在 [0, 99] ,0 则自适应
    min         = +min || 0;
    max         = +max || 0;
    minLocked   = maxLocked = 0;
    if ('min' in custOpts) {
        min     = +custOpts.min || 0;
        minLocked = 1;
    }
    if ('max' in custOpts) {
        max     = +custOpts.max || 0;
        maxLocked = 1;
    }
    if (min > max) {max = [min, min = max][0];}             // 最值交换
    var span    = max - min;
    if (minLocked && maxLocked) {
        return bothLocked(min, max, section);               // 两个最值同时被锁定,注意差值为 0 的情况
    }
    if (span < (section || 5)) {                            // 跨度值小于要分的段数,步长将会小于 1
        if (MATH_ISINT(min) && MATH_ISINT(max)) {           // 步长小于 1 同时两个最值都是整数,特别处理
            return forInteger(min, max, section);           // 也要考虑差值为 0 的情况
        }
        else if (span === 0) {                              // 非整数且跨度为 0 的情况
            return forSpan0(min, max, section);
        }
    }
    return coreCalc(min, max, section);                     // 非特殊情况的计算,须确保 min < max
}
 
 
 
/**
 * 构造返回值,处理小数精度等问题
 * @param   {Number}    newMin      最小值
 * @param   {Number}    newMax      最大值
 * @param   {Number}    section     分段数
 * @param   {Number}    [expon]     计算量级
 * @return  {Object}                同 smartSteps
 */
function makeResult(newMin, newMax, section, expon) {
    expon       = expon || 0;                               // 这是中间计算量级,受步长增长、特别是最值锁定的影响,可能会小于基准量级,因为整数部分被过度放大
    var expStep = expNum((newMax - newMin) / section, -1);
    var expMin  = expNum(newMin, -1, 1);                    // 锁定的最值有效数位可能很多,需要全精度保留
    var expMax  = expNum(newMax, -1);
    var minExp  = MT.min(expStep.e, expMin.e, expMax.e);    // 这个值实际上就是各值整数部分尾部多余的 0 的个数
    if (expMin.c === 0) {                                   // 0 可以有任意多个尾0
        minExp  = MT.min(expStep.e, expMax.e);
    } else if (expMax.c === 0) {
        minExp  = MT.min(expStep.e, expMin.e);
    }
    expFixTo(expStep, {c: 0, e: minExp});
    expFixTo(expMin, expStep, 1);
    expFixTo(expMax, expStep);
    expon      += minExp;                                   // 最终的基准量级,在这个量级下,各值刚好能表示成整数
    newMin      = expMin.c;
    newMax      = expMax.c;
    var step    = (newMax - newMin) / section;
    var zoom    = MATH_POW(expon);
    var fixTo   = 0;
    var points  = [];
    for (var i  = section + 1; i--;) {                      // 因为点数比段数多 1
        points[i] = (newMin + step * i) * zoom;             // 如果不涉及小数问题,这里就直接使用数值型
    }
    if (expon   < 0) {
        fixTo   = decimals(zoom);                           // 前面已经去掉了各值尾部多余的 0 ,所以 zoom 的小数位就是最终的 fix 位数
        step    = +(step * zoom).toFixed(fixTo);            // toFixed 处理长尾小数问题,如:0.2 * 0.1 = 0.020000000000000004
        newMin  = +(newMin * zoom).toFixed(fixTo);
        newMax  = +(newMax * zoom).toFixed(fixTo);
        for (var i = points.length; i--;) {
            points[i] = points[i].toFixed(fixTo);           // 为保证小数点对齐,统一转为字符型
            +points[i] === 0 && (points[i] = '0');          // 0.000 不好看
        }
    }
    else {
        newMin *= zoom;
        newMax *= zoom;
        step   *= zoom;
    }
    custSecs    = 0;
    custSteps   = 0;
    custOpts    = 0;
    // 这些公共变量可能持用了对用户参数的引用,这里是函数的最终出口,释放引用
 
    return {
        min:    newMin,                 // 新最小值
        max:    newMax,                 // 新最大值
        secs:   section,                // 分段数
        step:   step,                   // 每段长
        fix:    fixTo,                  // 小数保留位数,0 则为整数
        exp:    expon,                  // 基准量级,并非原值所在的量级,而是说在这个量级下,各值能表示成整数
        pnts:   points                  // 分段结果,整数都是数值型,小数时为了对齐小数点,都是字符型,但其中 0 不带小数点,即没有 "0.000"
    };
}
 
 
 
/**
 * 量级计数法 表示数值,不适用于很大或者很小的数,0 更不行
 * @param       {Number}    num             原数
 * @param       {Number}    [digit = 2]     精度位数,必须 => [1, 9]
 * @param       {Boolean}   [byFloor = 0]   默认为 0 表示近似值不小于原值,置 1 表示近似值不大于原值
 * @return      {Object}    {c: c, e: e}    c e 都是整数,c * 10 ^ e 即为原值的近似数
 * @description             返回值应该更详细一点:{c: c, e: e, d: d, n: n} ,其中 d 是 c 的位数,n = c * 10 ^ e ,不过目前好像不太有用
 */
function expNum(num, digit, byFloor) {
    digit       = MATH_ROUND(digit % 10) || 2;
    if (digit   < 0) {                                      // 全精度位数
        if (MATH_ISINT(num)) {                              // 整数的全精度位数,要去掉尾 0 ,但 0 也是整数,要专门留一位精度
            digit = ('' + MATH_ABS(num)).replace(/0+$/, '').length || 1;
        }
        else {                                              // 小数的全精度位数,要去掉首 0
            num = num.toFixed(15).replace(/0+$/, '');       // toFixed 处理长尾小数
            digit = num.replace('.', '').replace(/^[-0]+/, '').length;
            num = +num;                                     // '' + 0.0000001 会得到 '1e-7'
        }
    }
    var expon   = MATH_FLOOR(MATH_LOG(num)) - digit + 1;
    var cNum    = +(num * MATH_POW(-expon)).toFixed(15) || 0;   // toFixed 处理长尾小数问题
    cNum        = byFloor ? MATH_FLOOR(cNum) : MATH_CEIL(cNum); // 向上取整可能发生进位,使精度位数增加 1
    !cNum && (expon = 0);
    if (('' + MATH_ABS(cNum)).length > digit) {                 // 整数位数判断,字符串法比对数法快近一倍
        expon  += 1;
        cNum   /= 10;
    }
    return {
        c: cNum,
        e: expon
    };
}
 
 
/**
 * 将前者的指数对齐到后者,如果前者量级较小,就是强制加大指数,值误差可能严重放大,甚至值变为 0
 */
function expFixTo(expnum1, expnum2, byFloor) {
    var deltaExp    = expnum2.e - expnum1.e;
    if (deltaExp) {
        expnum1.e  += deltaExp;                             // 指数减小时,只需将整数部分相应放大
        expnum1.c  *= MATH_POW(-deltaExp);                  // 指数增加时,整数部分将缩小,就涉及 floor ceil 取整和变 0 问题
        expnum1.c   = byFloor ? MATH_FLOOR(expnum1.c) : MATH_CEIL(expnum1.c);
    }
}
 
 
/**
 * 将两个量级数的指数对齐到较小者
 */
function expFixMin(expnum1, expnum2, byFloor) {
    if (expnum1.e < expnum2.e) {
        expFixTo(expnum2, expnum1, byFloor);
    }
    else {
        expFixTo(expnum1, expnum2, byFloor);
    }
}
 
 
/**
 * 基于量级计数法,对原值的整数部分取近似,不适用于负数和 0
 * @param       {Number}    num             原值
 * @param       {Array}     [rounds]        在取近似时,提供预置选项,近似到 rounds 中的某项
 * @return      {Object}    expNum          2 位精度的量级计数法对象,不小于原值
 */
function getCeil(num, rounds) {
    rounds      = rounds || mySteps;
    num         = expNum(num);                              // 2 位精度量级计数法
    var cNum    = num.c;
    var i       = 0;
    while (cNum > rounds[i]) {                              // 在预置的近似数中,找到不小于目标 cNum 的项
        i++;
    }
    if (!rounds[i]) {                                       // 如果没找到合适的预置项,一定是目标值大于全部的预置项
        cNum   /= 10;                                       // 将目标值缩小 10 倍,重找一次定能命中
        num.e  += 1;
        i       = 0;
        while (cNum > rounds[i]) {
            i++;
        }
    }
    num.c       = rounds[i];
    return num;
}
 
 
 
 
/**
 * 基于量级计数法的计算,必须 min < max
 */
function coreCalc(min, max, section) {
    var step;
    var secs    = section || +custSecs.slice(-1);
    var expStep = getCeil((max - min) / secs, custSteps);   // 这是可能的最小步长,以它的量级作为后续计算的基准量级,以保证整数计算特性
    var expSpan = expNum(max - min);                        // 2 位精度的最值跨度,过高的精度意味着有效数位更多
    var expMin  = expNum(min, -1, 1);                       // 最小值向下近似,以涵盖原最小值
    var expMax  = expNum(max, -1);     // 最大值向上近似,参数 -1 表示保留全精度,因为要注意 min = 10000001, max = 10000002 等情况
    expFixTo(expSpan, expStep);                             // 指数对齐
    expFixTo(expMin, expStep, 1);                           // 经过指数对齐,原最大值、最小值都有可能变为 0
    expFixTo(expMax, expStep);
    if (!section) {
        secs    = look4sections(expMin, expMax);
    }
    else {
        step    = look4step(expMin, expMax, secs);
    }
 
    // 如果原最值都是整数,尽量让输出值也保持整数,但原最值跨 0 的则不调整
    if (MATH_ISINT(min) && MATH_ISINT(max) && min * max >= 0) {
        if (max - min < secs) {                             // 再次出现跨度小于段数
            return forInteger(min, max, secs);
        }
        secs = tryForInt(min, max, section, expMin, expMax, secs);
    }
    var arrMM   = cross0(min, max, expMin.c, expMax.c);
    expMin.c    = arrMM[0];
    expMax.c    = arrMM[1];
    if (minLocked || maxLocked) {
        singleLocked(min, max, expMin, expMax);
    }
    return makeResult(expMin.c, expMax.c, secs, expMax.e);
}
 
 
 
/**
 * 在预置的可选段数中,找出一个合适的值,让跨度误差尽量小
 */
function look4sections(expMin, expMax) {
    var section;
    var tmpStep, tmpMin, tmpMax;
    var reference   = [];
    for (var i      = custSecs.length; i--;) {              // 逐步减小段数,步长就会渐大
        section     = custSecs[i];
        tmpStep     = getCeil((expMax.c - expMin.c) / section, custSteps);
        tmpStep     = tmpStep.c * MATH_POW(tmpStep.e);      // 步长都用常规整数参与计算
        tmpMin      = MATH_FLOOR(expMin.c / tmpStep) * tmpStep;
        tmpMax      = MATH_CEIL(expMax.c / tmpStep) * tmpStep;
        reference[i] = {
            min:    tmpMin,
            max:    tmpMax,
            step:   tmpStep,
            span:   tmpMax - tmpMin                         // 步长的误差被 段数 成倍放大,可能会给跨度造成更大的误差,使最后的段数大于预置的最大值
        };
    }
    reference.sort(function (a, b) {
        var delta = a.span - b.span;                        // 分段调整之后的跨度,一定不小于原跨度,所以越小越好
        if (delta === 0) {
            delta = a.step - b.step;                        // 跨度相同时,步长小者胜出
        }
        return delta;
    });
    reference   = reference[0];
    section     = reference.span / reference.step;
    expMin.c    = reference.min;
    expMax.c    = reference.max;
    return section < 3 ? section * 2 : section;             // 如果最终步长比最小步长大得多,段数就可能变得很小
}
 
 
/**
 * 指定段数,在预置的可选步长中,找出一个合适的值,让 步长 * 段数 积刚好涵盖原最大值与最小值
 */
function look4step(expMin, expMax, secs) {
    var span;
    var tmpMax;
    var tmpMin      = expMax.c;
    var tmpStep     = (expMax.c - expMin.c) / secs - 1;
    while (tmpMin   > expMin.c) {
        tmpStep     = getCeil(tmpStep + 1, custSteps);
        tmpStep     = tmpStep.c * MATH_POW(tmpStep.e);
        span        = tmpStep * secs;
        tmpMax      = MATH_CEIL(expMax.c / tmpStep) * tmpStep;
        tmpMin      = tmpMax - span;                        // 优先保证 max 端的误差最小,试看原 min 值能否被覆盖到
    }
    var deltaMin    = expMin.c - tmpMin;                    // 上面的计算可能会让 min 端的误差更大,下面尝试均衡误差
    var deltaMax    = tmpMax - expMax.c;
    var deltaDelta  = deltaMin - deltaMax;
    if (deltaDelta  > tmpStep * 1.1) {                      // 当 min 端的误差比 max 端大很多时,考虑将 tmpMin tmpMax 同时上移
        deltaDelta  = MATH_ROUND(deltaDelta / tmpStep / 2) * tmpStep;
        tmpMin     += deltaDelta;
        tmpMax     += deltaDelta;
    }
    expMin.c   = tmpMin;
    expMax.c   = tmpMax;
    return tmpStep;
}
 
 
/**
 * 原最值都是整数时,尝试让输出也保持整数
 */
function tryForInt(min, max, section, expMin, expMax, secs) {
    var span = expMax.c - expMin.c;
    var step = span / secs * MATH_POW(expMax.e);
    if (!MATH_ISINT(step)) {                                // 原最值都是整数,但计算步长可能出现小数,如 2.5
        step = MATH_FLOOR(step);                            // 步长总是要尽量小,以减小跨度误差,所以 2.5 可能被调整为 2 或者 3
        span = step * secs;
        if (span < max - min) {
            step += 1;
            span = step * secs;
            if (!section && (step * (secs - 1) >= (max - min))) {
                secs -= 1;
                span = step * secs;
            }
        }
        if (span >= max - min) {
            var delta   = span - (max - min);               // 误差均衡
            expMin.c    = MATH_ROUND(min - delta / 2);
            expMax.c    = MATH_ROUND(max + delta / 2);
            expMin.e    = 0;
            expMax.e    = 0;
        }
    }
    return secs;
}
 
 
 
 
/**
 * 整数情况下,跨度小于段数的处理
 */
function forInteger(min, max, section) {
    section     = section || 5;
    if (minLocked) {
        max     = min + section;                            // min max 没有写错,因为 min locked 所以 max 在 min 上浮动
    }
    else if (maxLocked) {
        min     = max - section;
    }
    else {
        var delta   = section - (max - min);                // 没有端点锁定时,向上下延展跨度
        var newMin  = MATH_ROUND(min - delta / 2);
        var newMax  = MATH_ROUND(max + delta / 2);
        var arrMM   = cross0(min, max, newMin, newMax);     // 避免跨 0
        min         = arrMM[0];
        max         = arrMM[1];
    }
    return makeResult(min, max, section);
}
 
 
/**
 * 非整数情况下,跨度为 0 的处理
 */
function forSpan0(min, max, section) {
    section     = section || 5;
    // delta 一定不为 0 ,因为 min === max === 0 的情况会进入 forInteger 分支
    var delta   = MT.min(MATH_ABS(max / section), section) / 2.1;
    if (minLocked) {
        max     = min + delta;                              // min max 没有写错,因为 min locked 所以 max 在 min 上浮动
    }
    else if (maxLocked) {
        min     = max - delta;
    }
    else {                                                  // 以最值为中心,上下各延展一小段
        min     = min - delta;
        max     = max + delta;
    }
    return coreCalc(min, max, section);
}
 
 
/**
 * 当原始最值都在 0 的同侧时,让输出也保持在 0 的同侧
 */
function cross0(min, max, newMin, newMax) {
    if (min >= 0 && newMin < 0) {
        newMax -= newMin;
        newMin  = 0;
    }
    else if (max <= 0 && newMax > 0) {
        newMin -= newMax;
        newMax  = 0;
    }
    return [newMin, newMax];
}
 
 
/**
 * 取一个数的小数位数
 * @param   {Number}    num         原数值
 * @return  {Number}    decimals    整数则返回 0 ,小数则返回小数点后的位数
 */
function decimals(num) {
    num = (+num).toFixed(15).split('.');                    // String(0.0000001) 会得到 '1e-7'
    return num.pop().replace(/0+$/, '').length;
}
 
 
 
 
 
 
/**
 * 单个最值锁定处理,只是在原计算的基础上,锁定一个,平移另一个
 */
function singleLocked(min, max, emin, emax) {
    if (minLocked) {
        var expMin  = expNum(min, 4, 1);                    // 4 位精度向下近似
        if (emin.e  - expMin.e > 6) {                       // 如果锁定值的量级远小于基准量级,认为锁定失败,强置为 0
            expMin  = {c: 0, e: emin.e};
        }
        expFixMin(emin, expMin);                            // 将指数与量级较小者对齐
        expFixMin(emax, expMin);
        emax.c     += expMin.c - emin.c;                    // 最大值平移
        emin.c      = expMin.c;                             // 最小值锁定
    }
    else if (maxLocked) {
        var expMax  = expNum(max, 4);                       // 4 位精度向上近似
        if (emax.e  - expMax.e > 6) {                       // 如果锁定值的量级远小于基准量级,认为锁定失败,强置为 0
            expMax  = {c: 0, e: emax.e};
        }
        expFixMin(emin, expMax);                            // 将指数与量级较小者对齐
        expFixMin(emax, expMax);
        emin.c     += expMax.c - emax.c;                    // 最小值平移
        emax.c      = expMax.c;                             // 最大值锁定
    }
}
 
 
/**
 * 最小值和最大值同时被锁定的情况在这里,其它地方只考虑单边最值锁定
 * @param   {Number}    min         锁定的最小值
 * @param   {Number}    max         锁定的最大值
 * @param   {Number}    [section]   段数
 * @return  {Object}                同 smartSteps
 */
function bothLocked(min, max, section) {
    var trySecs     = section ? [section] : custSecs;
    var span        = max - min;
    if (span       === 0) {                                 // 最大最小值都锁定到同一个值上,认为锁定失败
        max         = expNum(max, 3);                       // 3 位精度向上近似
        section     = trySecs[0];
        max.c       = MATH_ROUND(max.c + section / 2);
        return makeResult(max.c - section, max.c, section, max.e);
    }
    if (MATH_ABS(max / span) < 1e-6) {                      // 如果锁定值远小于跨度,也认为锁定失败,强置为 0
        max         = 0;
    }
    if (MATH_ABS(min / span) < 1e-6) {
        min         = 0;
    }
    var step, deltaSpan, score;
    var scoreS      = [[5, 10], [10, 2], [50, 10], [100, 2]];
    var reference   = [];
    var debugLog    = [];
    var expSpan     = expNum((max - min), 3);               // 3 位精度向上近似
    var expMin      = expNum(min, -1, 1);
    var expMax      = expNum(max, -1);
    expFixTo(expMin, expSpan, 1);
    expFixTo(expMax, expSpan);
    span            = expMax.c - expMin.c;
    expSpan.c       = span;
    
    for (var i      = trySecs.length; i--;) {
        section     = trySecs[i];
        step        = MATH_CEIL(span / section);
        deltaSpan   = step * section - span;
        score       = (deltaSpan + 3) * 3;                  // 误差越大得分越高
        score      += (section - trySecs[0] + 2) * 2;       // 分段越多得分越高
        if (section % 5 === 0) {                            // 段数为 5 可以减分
            score  -= 10;
        }
        for (var j  = scoreS.length; j--;) {                // 好的步长是最重要的减分项
            if (step % scoreS[j][0] === 0) {
                score /= scoreS[j][1];
            }
        }
        debugLog[i] = [section, step, deltaSpan, score].join();
        reference[i] = {
            secs:   section,
            step:   step,
            delta:  deltaSpan,
            score:  score
        };
    }
    //console.log(debugLog);
    reference.sort(function (a, b) {return a.score - b.score;});
    reference   = reference[0];
    expMin.c    = MATH_ROUND(expMin.c - reference.delta / 2);
    expMax.c    = MATH_ROUND(expMax.c + reference.delta / 2);
    return makeResult(expMin.c, expMax.c, reference.secs, expSpan.e);
}
 
 
 
 
return smartSteps;
});