-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathLibDecimalFloat.sol
More file actions
729 lines (663 loc) · 33.7 KB
/
Copy pathLibDecimalFloat.sol
File metadata and controls
729 lines (663 loc) · 33.7 KB
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
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;
import {
LOG_TABLES,
LOG_TABLES_SMALL,
LOG_TABLES_SMALL_ALT,
ANTI_LOG_TABLES,
ANTI_LOG_TABLES_SMALL
} from "../generated/LogTables.pointers.sol";
import {
ExponentOverflow,
CoefficientOverflow,
Log10Zero,
NegativeFixedDecimalConversion,
LossyConversionFromFloat,
LossyConversionToFloat,
ZeroNegativePower
} from "../error/ErrDecimalFloat.sol";
import {
LibDecimalFloatImplementation,
EXPONENT_MAX,
EXPONENT_MIN
} from "./implementation/LibDecimalFloatImplementation.sol";
type Float is bytes32;
/// @title LibDecimalFloat
/// Floating point math library for Rainlang.
/// Broadly implements decimal floating point math with 224 signed bits for the
/// coefficient and 32 signed bits for the exponent. Notably the implementation
/// differs from standard specifications in a few key areas:
///
/// - There is no concept of NaN or Infinity.
/// - There is no concept of rounding modes.
/// - There is no negative zero.
/// - This is a decimal floating point library, not binary.
///
/// This means that operations such as divide by 0 will revert, rather than
/// produce nonsense like NaN or Infinity. This is a deliberate design choice
/// to make the library more predictable and easier to reason about as the basis
/// of a defi native smart contract language.
///
/// The reason that this is a decimal floating point system is that the inputs
/// to the system as rainlang literals are decimal values. This means that `0.1`
/// has an _exact_ representation in the system, rather than a repeating binary
/// fraction. This technically results in less precision than a binary floating
/// point system, but is much more predictable and easier to reason about in the
/// context of financial inputs and outputs, which are typically all decimal
/// values as understood by humans. However, consider that we have 224 bits of
/// precision in the coefficient, which is far more than the 53 bits of a double
/// precision floating point number regardless of binary/decimal considerations,
/// and should be more than enough for most defi use cases.
library LibDecimalFloat {
using LibDecimalFloat for Float;
address constant LOG_TABLES_ADDRESS = 0x295180b25A5059a2e7eC64272ba4F85047B4146A;
/// A zero valued float.
Float constant FLOAT_ZERO = Float.wrap(0);
/// A one valued float.
Float constant FLOAT_ONE = Float.wrap(bytes32(uint256(1)));
/// A half valued float.
// slither-disable-next-line too-many-digits
Float constant FLOAT_HALF =
Float.wrap(bytes32(uint256(0xffffffff00000000000000000000000000000000000000000000000000000005)));
/// A two valued float.
Float constant FLOAT_TWO = Float.wrap(bytes32(uint256(0x02)));
/// Largest possible positive value.
/// type(int224).max, type(int32).max
Float constant FLOAT_MAX_POSITIVE_VALUE =
Float.wrap(bytes32(uint256(0x7fffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffff)));
/// Smallest possible positive value.
/// 1, type(int32).min
// slither-disable-next-line too-many-digits
Float constant FLOAT_MIN_POSITIVE_VALUE =
Float.wrap(bytes32(uint256(0x8000000000000000000000000000000000000000000000000000000000000001)));
/// Largest possible (closest to zero) negative value.
/// -1, type(int32).min
// slither-disable-next-line too-many-digits
Float constant FLOAT_MAX_NEGATIVE_VALUE =
Float.wrap(bytes32(uint256(0x80000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff)));
/// Smallest possible (most negative) negative value.
/// type (int224).min, type(int32).max
// slither-disable-next-line too-many-digits
Float constant FLOAT_MIN_NEGATIVE_VALUE =
Float.wrap(bytes32(uint256(0x7fffffff80000000000000000000000000000000000000000000000000000000)));
/// Euler's number
/// 2.718281828459045235360287471352662497757247093699959574966967627724e66, -66
Float constant FLOAT_E =
Float.wrap(bytes32(uint256(0xffffffbe19cfc6ef4f44cf88f14500d013df534fcaad48fca1d5ca47bea26fcc)));
/// Convert a fixed point decimal value to a signed coefficient and exponent.
/// The conversion can be lossy if the unsigned value is too large to fit in
/// the signed coefficient.
/// @param value The fixed point decimal value to convert.
/// @param decimals The number of decimals in the fixed point representation.
/// e.g. If 1e18 represents 1 this would be 18 decimals.
/// @return signedCoefficient The signed coefficient of the floating point
/// representation.
/// @return exponent The exponent of the floating point representation.
/// @return lossless `true` if the conversion is lossless.
function fromFixedDecimalLossy(uint256 value, uint8 decimals) internal pure returns (int256, int256, bool) {
unchecked {
int256 exponent = -int256(uint256(decimals));
// Catch an edge case where unsigned value looks like a negative
// value when coerced.
if (value > uint256(type(int256).max)) {
return (int256(value / 10), exponent + 1, value % 10 == 0);
} else {
return (int256(value), exponent, true);
}
}
}
/// Same as fromFixedDecimalLossy, but returns a Float struct instead of
/// separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param value The fixed point decimal value to convert.
/// @param decimals The number of decimals in the fixed point representation.
/// e.g. If 1e18 represents 1 this would be 18 decimals.
/// @return float The Float struct containing the signed coefficient and
/// exponent.
/// @return lossless `true` if the conversion is lossless.
function fromFixedDecimalLossyPacked(uint256 value, uint8 decimals) internal pure returns (Float, bool) {
(int256 signedCoefficient, int256 exponent, bool lossless) = fromFixedDecimalLossy(value, decimals);
(Float float, bool losslessPack) = packLossy(signedCoefficient, exponent);
return (float, lossless && losslessPack);
}
/// Lossless version of `fromFixedDecimalLossy`. This will revert if the
/// conversion is lossy.
/// @param value As per `fromFixedDecimalLossy`.
/// @param decimals As per `fromFixedDecimalLossy`.
/// @return signedCoefficient As per `fromFixedDecimalLossy`.
/// @return exponent As per `fromFixedDecimalLossy`.
function fromFixedDecimalLossless(uint256 value, uint8 decimals) internal pure returns (int256, int256) {
(int256 signedCoefficient, int256 exponent, bool lossless) = fromFixedDecimalLossy(value, decimals);
if (!lossless) {
revert LossyConversionToFloat(signedCoefficient, exponent);
}
return (signedCoefficient, exponent);
}
/// Lossless version of `fromFixedDecimalLossyMem`. This will revert if the
/// conversion is lossy.
/// @param value As per `fromFixedDecimalLossyMem`.
/// @param decimals As per `fromFixedDecimalLossyMem`.
/// @return float The Float struct containing the signed coefficient and
/// exponent.
function fromFixedDecimalLosslessPacked(uint256 value, uint8 decimals) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = fromFixedDecimalLossless(value, decimals);
return packLossless(signedCoefficient, exponent);
}
/// Convert a signed coefficient and exponent to a fixed point decimal value.
/// The conversion is impossible and will revert if the signed coefficient is
/// negative. If the conversion overflows it will also revert.
/// The conversion can be lossy if the floating point representation is not
/// able to fit in the fixed point representation, and will truncate
/// precision.
/// @param signedCoefficient The signed coefficient of the floating point
/// representation.
/// @param exponent The exponent of the floating point representation.
/// @param decimals The number of decimals in the fixed point representation.
/// e.g. If 1e18 represents 1 this would be 18 decimals.
/// @return value The fixed point decimal value.
/// @return lossless `true` if the conversion is lossless.
function toFixedDecimalLossy(int256 signedCoefficient, int256 exponent, uint8 decimals)
internal
pure
returns (uint256, bool)
{
// The output type is uint256, so we can't represent negative numbers.
if (signedCoefficient < 0) {
revert NegativeFixedDecimalConversion(signedCoefficient, exponent);
}
// Zero is always 0 and neither exponent nor decimals matter.
else if (signedCoefficient == 0) {
return (0, true);
} else {
// Safe to do this conversion because we revert above on negative.
uint256 unsignedCoefficient = uint256(signedCoefficient);
int256 finalExponent;
// Ye olde "safe math" to give a better error if this edge case
// overflow is ever hit. Normal use should never overflow here.
unchecked {
finalExponent = exponent + int256(uint256(decimals));
if (finalExponent < exponent) {
revert ExponentOverflow(signedCoefficient, exponent);
}
}
uint256 scale;
uint256 fixedDecimal;
if (finalExponent < 0) {
unchecked {
// Every possible value rounds to 0 if the exponent is less
// than -77. This is always lossless as we know the value is
// is not zero in real.
if (finalExponent < -77) {
return (0, false);
}
// At this point, scale cannot revert, so it is safe to do
// this unchecked.
scale = 10 ** uint256(-finalExponent);
fixedDecimal = unsignedCoefficient / scale;
// Slither false positive because we're explicitly checking
// for the lossiness that it warns about.
//slither-disable-next-line divide-before-multiply
return (fixedDecimal, fixedDecimal * scale == unsignedCoefficient);
}
} else if (finalExponent > 0) {
scale = 10 ** uint256(finalExponent);
fixedDecimal = unsignedCoefficient * scale;
unchecked {
// This is always lossless because we're scaling up.
// If the value is too large to fit in a uint256, we'll
// revert above due to overflow.
return (fixedDecimal, true);
}
} else {
return (unsignedCoefficient, true);
}
}
}
/// Same as toFixedDecimalLossy, but accepts a Float struct instead of
/// separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param float The Float struct containing the signed coefficient and
/// exponent.
/// @param decimals The number of decimals in the fixed point representation.
/// e.g. If 1e18 represents 1 this would be 18 decimals.
/// @return value The fixed point decimal value.
/// @return lossless `true` if the conversion is lossless.
function toFixedDecimalLossy(Float float, uint8 decimals) internal pure returns (uint256, bool) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
return toFixedDecimalLossy(signedCoefficient, exponent, decimals);
}
/// Lossless version of `toFixedDecimalLossy`. This will revert if the
/// conversion is lossy.
/// @param signedCoefficient As per `toFixedDecimalLossy`.
/// @param exponent As per `toFixedDecimalLossy`.
/// @param decimals As per `toFixedDecimalLossy`.
/// @return value As per `toFixedDecimalLossy`.
function toFixedDecimalLossless(int256 signedCoefficient, int256 exponent, uint8 decimals)
internal
pure
returns (uint256)
{
(uint256 value, bool lossless) = toFixedDecimalLossy(signedCoefficient, exponent, decimals);
if (!lossless) {
revert LossyConversionFromFloat(signedCoefficient, exponent);
}
return value;
}
/// Same as toFixedDecimalLossless, but accepts a Float struct instead of
/// separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param float The Float struct containing the signed coefficient and
/// exponent.
/// @param decimals The number of decimals in the fixed point representation.
/// e.g. If 1e18 represents 1 this would be 18 decimals.
/// @return value The fixed point decimal value.
function toFixedDecimalLossless(Float float, uint8 decimals) internal pure returns (uint256) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
return toFixedDecimalLossless(signedCoefficient, exponent, decimals);
}
/// Pack a signed coefficient and exponent into a single `PackedFloat`.
/// Clearly this involves fitting 64 bytes into 32 bytes, so there will be
/// data loss.
/// @param signedCoefficient The signed coefficient of the floating point
/// representation.
/// @param exponent The exponent of the floating point representation.
/// @return float The packed representation of the signed coefficient and
/// exponent.
function packLossy(int256 signedCoefficient, int256 exponent) internal pure returns (Float float, bool lossless) {
unchecked {
lossless = int224(signedCoefficient) == signedCoefficient;
// The reason that we can do unchecked exponent addition here is that
// when it overflows it will wrap to a very large negative number.
// This will get caught below when we check if the exponent fits in
// int32.
if (!lossless) {
if (signedCoefficient / 1e72 != 0) {
signedCoefficient /= 1e5;
exponent += 5;
}
while (int224(signedCoefficient) != signedCoefficient) {
signedCoefficient /= 10;
++exponent;
}
}
if (int32(exponent) != exponent) {
revert ExponentOverflow(signedCoefficient, exponent);
}
// Need a mask to zero out the bits that could be set to 1 if the
// coefficient is negative.
uint256 mask = type(uint224).max;
assembly ("memory-safe") {
float := or(and(signedCoefficient, mask), shl(0xe0, exponent))
}
}
}
function packLossless(int256 signedCoefficient, int256 exponent) internal pure returns (Float) {
(Float c, bool lossless) = packLossy(signedCoefficient, exponent);
if (!lossless) {
revert CoefficientOverflow(signedCoefficient, exponent);
}
return c;
}
/// Unpack a packed bytes32 into a signed coefficient and exponent. This is
/// the inverse of `pack`.
/// @param float The packed representation of the signed coefficient and
/// exponent.
/// @return signedCoefficient The signed coefficient of the floating point
/// representation.
/// @return exponent The exponent of the floating point representation.
function unpack(Float float) internal pure returns (int256 signedCoefficient, int256 exponent) {
uint256 mask = type(uint224).max;
assembly ("memory-safe") {
signedCoefficient := signextend(27, and(float, mask))
exponent := sar(0xe0, float)
}
}
/// Same as add, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param a The Float struct containing the signed coefficient and
/// exponent of the first floating point number.
/// @param b The Float struct containing the signed coefficient and
/// exponent of the second floating point number.
function add(Float a, Float b) internal pure returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(int256 signedCoefficient, int256 exponent) =
LibDecimalFloatImplementation.add(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
(Float c, bool lossless) = packLossy(signedCoefficient, exponent);
// Addition can be lossy.
(lossless);
return c;
}
/// Subtract float a from float b.
///
/// This is effectively shorthand for adding the two floats with the second
/// float negated. Therefore, the same caveats apply as for `add`.
/// @param a The float to subtract from.
/// @param b The float to subtract.
function sub(Float a, Float b) internal pure returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(int256 signedCoefficientC, int256 exponentC) =
LibDecimalFloatImplementation.sub(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
(Float c, bool lossless) = packLossy(signedCoefficientC, exponentC);
// Subtraction can be lossy.
(lossless);
return c;
}
/// Same as minus, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param float The Float struct containing the signed coefficient and
/// exponent of the floating point number.
function minus(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
(signedCoefficient, exponent) = LibDecimalFloatImplementation.minus(signedCoefficient, exponent);
(Float result, bool lossless) = packLossy(signedCoefficient, exponent);
// Minus is a lossy operation due to the asymmetry of signed integers.
(lossless);
return result;
}
/// Returns the absolute value of a float.
/// Identity if non-negative, negated if negative. Max negative signed value
/// for the coefficient will be shifted one OOM so that it can be negated to
/// a positive value.
///
/// https://speleotrove.com/decimal/daops.html#refabs
/// > abs takes one operand. If the operand is negative, the result is the
/// > same as using the minus operation on the operand. Otherwise, the result
/// > is the same as using the plus operation on the operand.
/// @param float The float to take the absolute value of.
function abs(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
if (signedCoefficient < 0) {
(signedCoefficient, exponent) = LibDecimalFloatImplementation.minus(signedCoefficient, exponent);
}
(Float result, bool lossless) = packLossy(signedCoefficient, exponent);
// At the limit of signed values there is the potential for a lossy
// conversion when negating.
(lossless);
return result;
}
/// https://speleotrove.com/decimal/daops.html#refmult
/// > multiply takes two operands. If either operand is a special value then
/// > the general rules apply.
/// >
/// > Otherwise, the operands are multiplied together
/// > (‘long multiplication’), resulting in a number which may be as long as
/// > the sum of the lengths of the two operands, as follows:
/// >
/// > - The coefficient of the result, before rounding, is computed by
/// > multiplying together the coefficients of the operands.
/// > - The exponent of the result, before rounding, is the sum of the
/// > exponents of the two operands.
/// > - The sign of the result is the exclusive or of the signs of the
/// > operands.
/// >
/// > The result is then rounded to precision digits if necessary, counting
/// > from the most significant digit of the result.
/// @param a The Float struct containing the signed coefficient and
/// exponent of the first floating point number.
/// @param b The Float struct containing the signed coefficient and
/// exponent of the second floating point number.
function mul(Float a, Float b) internal pure returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(int256 signedCoefficient, int256 exponent) =
LibDecimalFloatImplementation.mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
(Float c, bool lossless) = packLossy(signedCoefficient, exponent);
// Multiplication is typically lossless, but can be lossy in edge cases.
(lossless);
return c;
}
/// Same as divide, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param a The Float struct containing the signed coefficient and
/// exponent of the first floating point number.
/// @param b The Float struct containing the signed coefficient and
/// exponent of the second floating point number.
function div(Float a, Float b) internal pure returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(int256 signedCoefficient, int256 exponent) =
LibDecimalFloatImplementation.div(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
(Float c, bool lossless) = packLossy(signedCoefficient, exponent);
// Division is often lossy because it is very easy to end up with
// infinite decimal representations.
(lossless);
return c;
}
/// Same as inv, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param float The Float struct containing the signed coefficient and
/// exponent of the floating point number.
function inv(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
(signedCoefficient, exponent) = LibDecimalFloatImplementation.inv(signedCoefficient, exponent);
(Float result, bool lossless) = packLossy(signedCoefficient, exponent);
(lossless);
return result;
}
/// Same as eq, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param a The first float to compare.
/// @param b The second float to compare.
function eq(Float a, Float b) internal pure returns (bool) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
return LibDecimalFloatImplementation.eq(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
}
/// Numeric less than for floats.
/// A float is less than another if its numeric value is less than the other.
/// For example, 1e2 is less than 1e3, and 1e2 is less than 2e2.
/// @param a The first float to compare.
/// @param b The second float to compare.
function lt(Float a, Float b) internal pure returns (bool) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(signedCoefficientA, signedCoefficientB) =
LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
return signedCoefficientA < signedCoefficientB;
}
/// Numeric greater than for floats.
/// A float is greater than another if its numeric value is greater than the
/// other. For example, 1e3 is greater than 1e2, and 2e2 is greater than 1e2.
/// @param a The first float to compare.
/// @param b The second float to compare.
function gt(Float a, Float b) internal pure returns (bool) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(signedCoefficientA, signedCoefficientB) =
LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
return signedCoefficientA > signedCoefficientB;
}
/// Numeric less than or equal to for floats.
/// A float is less than or equal to another if its numeric value is less
/// than or equal to the other. For example, 1e2 is less than or equal to 1e3
/// and 1e2 is less than or equal to 1e2.
function lte(Float a, Float b) internal pure returns (bool) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(signedCoefficientA, signedCoefficientB) =
LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
return signedCoefficientA <= signedCoefficientB;
}
/// Numeric greater than or equal to for floats.
/// A float is greater than or equal to another if its numeric value is
/// greater than or equal to the other. For example, 1e3 is greater than or
/// equal to 1e2 and 1e2 is greater than or equal to 1e2.
function gte(Float a, Float b) internal pure returns (bool) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(signedCoefficientA, signedCoefficientB) =
LibDecimalFloatImplementation.compareRescale(signedCoefficientA, exponentA, signedCoefficientB, exponentB);
return signedCoefficientA >= signedCoefficientB;
}
/// Fractional component of a float.
/// @param float The float to frac.
function frac(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
(int256 characteristic, int256 mantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
(characteristic);
(Float result, bool lossless) = packLossy(mantissa, exponent);
// Frac is lossy by definition.
(lossless);
return result;
}
/// Integer component of a float.
/// @param float The float to floor.
function floor(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
// If the exponent is 0 or greater then the float is already an integer.
if (exponent >= 0) {
return float;
}
(int256 characteristic, int256 mantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
(Float result, bool lossless) = packLossy(characteristic, exponent);
// Flooring is lossy by definition.
(lossless, mantissa);
return result;
}
/// Smallest integer value greater than or equal to the float.
/// @param float The float to ceil.
function ceil(Float float) internal pure returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
// If the exponent is 0 or greater then the float is already an integer.
if (exponent >= 0) {
return float;
}
(int256 characteristic, int256 mantissa) =
LibDecimalFloatImplementation.characteristicMantissa(signedCoefficient, exponent);
// If the mantissa is 0, then the float is already an integer.
if (mantissa == 0) {
return float;
}
// Truncate the fractional part when exponent < 0:
// mantissa < 0 (input < 0) → truncation towards zero increases the value (correct ceil).
// mantissa == 0 → value is already an integer.
// mantissa > 0 (input > 0) → truncation decreases the value, so add 1 to round up.
else if (mantissa > 0) {
(characteristic, exponent) = LibDecimalFloatImplementation.add(characteristic, exponent, 1e76, -76);
}
(Float result, bool lossless) = packLossy(characteristic, exponent);
(lossless);
return result;
}
/// Same as power10, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param float The Float struct containing the signed coefficient and
/// exponent of the floating point number.
/// @param tablesDataContract The address of the contract containing the
/// logarithm tables.
function pow10(Float float, address tablesDataContract) internal view returns (Float) {
(int256 signedCoefficient, int256 exponent) = float.unpack();
(signedCoefficient, exponent) =
LibDecimalFloatImplementation.pow10(tablesDataContract, signedCoefficient, exponent);
(Float result, bool lossless) = packLossy(signedCoefficient, exponent);
// We don't care if power10 is lossy because it's an approximation
// anyway.
(lossless);
return result;
}
/// Same as log10, but accepts a Float struct instead of separate values.
/// Costs more gas but helps mitigate stack depth issues, and is more
/// ergonomic for the caller.
/// @param tablesDataContract The address of the contract containing the
/// logarithm tables.
/// @param a The float to log10.
function log10(Float a, address tablesDataContract) internal view returns (Float) {
(int256 signedCoefficient, int256 exponent) = a.unpack();
(signedCoefficient, exponent) =
LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficient, exponent);
(Float result, bool lossless) = packLossy(signedCoefficient, exponent);
// We don't care if log10 is lossy because it's an approximation anyway.
(lossless);
return result;
}
/// a^b = 10^(b * log10(a))
///
/// Due to the inaccuraces of log10 and power10, this is not perfectly
/// accurate, a round trip like x^y^(1/y) will typically be within half a
/// percent or less of the original value, but this can vary depending on
/// the input values.
///
/// Doesn't lose precision due to the exponent, for a wide range of
/// exponents.
/// @param a The float `a` in `a^b`.
/// @param b The float `b` in `a^b`.
/// @param tablesDataContract The address of the contract containing the
/// logarithm tables.
function pow(Float a, Float b, address tablesDataContract) internal view returns (Float) {
(int256 signedCoefficientA, int256 exponentA) = a.unpack();
if (b.isZero()) {
return FLOAT_ONE;
} else if (signedCoefficientA == 0) {
if (b.lt(FLOAT_ZERO)) {
// If b is negative, and a is 0, so we revert.
revert ZeroNegativePower(b);
}
// If a is zero, then a^b is always zero, regardless of b.
// This is a special case because log10(0) is undefined.
return FLOAT_ZERO;
}
(int256 signedCoefficientC, int256 exponentC) =
LibDecimalFloatImplementation.log10(tablesDataContract, signedCoefficientA, exponentA);
(int256 signedCoefficientB, int256 exponentB) = b.unpack();
(signedCoefficientC, exponentC) =
LibDecimalFloatImplementation.mul(signedCoefficientC, exponentC, signedCoefficientB, exponentB);
(signedCoefficientC, exponentC) =
LibDecimalFloatImplementation.pow10(tablesDataContract, signedCoefficientC, exponentC);
(Float c, bool lossless) = packLossy(signedCoefficientC, exponentC);
// We don't care if power is lossy because it's an approximation anyway.
(lossless);
return c;
}
/// sqrt a = a ^ 0.5
///
/// Due to the inaccuracies of log10 and power10, this is not perfectly
/// accurate, a round trip like sqrt(x)^2 will typically be within half a
/// percent or less of the original value, but this can vary depending on
/// the input values.
///
/// Doesn't lose precision due to the exponent, for a wide range of
/// exponents.
/// @param a The float to take the square root of.
/// @param tablesDataContract The address of the contract containing the
/// logarithm tables.
function sqrt(Float a, address tablesDataContract) internal view returns (Float) {
return pow(a, FLOAT_HALF, tablesDataContract);
}
/// Returns the minimum of two values.
/// Convenience for `a < b ? a : b`.
/// @param a The first float to compare.
/// @param b The second float to compare.
/// @return The minimum of the two floats.
function min(Float a, Float b) internal pure returns (Float) {
return lt(a, b) ? a : b;
}
/// Returns the maximum of two values.
/// Convenience for `a > b ? a : b`.
/// @param a The first float to compare.
/// @param b The second float to compare.
function max(Float a, Float b) internal pure returns (Float) {
return gt(a, b) ? a : b;
}
function isZero(Float a) internal pure returns (bool result) {
uint256 mask = type(uint224).max;
assembly ("memory-safe") {
// Don't need to signextend here because we only care if the value
// is zero or not.
result := iszero(and(a, mask))
}
}
}