Skip to content

Commit

Permalink
Fix u256 logarithm rounding errors (#6163)
Browse files Browse the repository at this point in the history
## Description
The current implementation of u256 logarithm will frequently output
wrong values due to rounding errors

Closes #6126

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: IGI-111 <igi-111@protonmail.com>
  • Loading branch information
SwayStar123 and IGI-111 committed Jul 1, 2024
1 parent d922748 commit aad97e8
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 8 deletions.
8 changes: 4 additions & 4 deletions sway-lib-std/src/flags.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ library;
use ::{assert::assert, logging::log, registers::{error, flags}};

// Mask second bit, which is `F_WRAPPING`.
const F_WRAPPING_DISABLE_MASK: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000010;
pub const F_WRAPPING_DISABLE_MASK: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000010;
// Mask second bit, which is `F_WRAPPING`.
const F_WRAPPING_ENABLE_MASK: u64 = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111101;
pub const F_WRAPPING_ENABLE_MASK: u64 = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111101;
// Mask first bit, which is `F_UNSAFEMATH`.
const F_UNSAFEMATH_DISABLE_MASK: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000001;
pub const F_UNSAFEMATH_DISABLE_MASK: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000001;
// Mask first bit, which is `F_UNSAFEMATH`.
const F_UNSAFEMATH_ENABLE_MASK: u64 = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111110;
pub const F_UNSAFEMATH_ENABLE_MASK: u64 = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111110;

/// Sets the flag register to the given value.
///
Expand Down
2 changes: 1 addition & 1 deletion sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ pub mod registers;
pub mod iterator;
pub mod vec;
pub mod bytes;
pub mod math;
pub mod flags;
pub mod math;
pub mod u128;
pub mod b512;
pub mod primitive_conversions;
Expand Down
63 changes: 60 additions & 3 deletions sway-lib-std/src/math.sw
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Utilities for common math operations.
library;

use ::assert::*;
use ::flags::{disable_panic_on_overflow, F_UNSAFEMATH_DISABLE_MASK, set_flags};
use ::registers::{flags, overflow};

/// Calculates the square root.
pub trait Root {
fn sqrt(self) -> Self;
Expand Down Expand Up @@ -213,8 +217,12 @@ impl BinaryLogarithm for u8 {

impl BinaryLogarithm for u256 {
fn log2(self) -> Self {
use ::assert::*;
assert(self != 0);
// If panic on unsafe math is enabled, only then revert
if flags() & F_UNSAFEMATH_DISABLE_MASK == 0 {
// Logarithm is undefined for 0
assert(self != 0);
}

let (a, b, c, d) = asm(r1: self) {
r1: (u64, u64, u64, u64)
};
Expand All @@ -233,9 +241,58 @@ impl BinaryLogarithm for u256 {

impl Logarithm for u256 {
fn log(self, base: Self) -> Self {
let flags = disable_panic_on_overflow();

// If panic on unsafe math is enabled, only then revert
if flags & F_UNSAFEMATH_DISABLE_MASK == 0 {
// Logarithm is undefined for bases less than 2
assert(base >= 2);
// Logarithm is undefined for 0
assert(self != 0);
}

// Decimals rounded to 0
if self < base {
return 0x00u256;
}

// Estimating the result using change of base formula. Only an estimate because we are doing uint calculations.
let self_log2 = self.log2();
let base_log2 = base.log2();
self_log2 / base_log2
let mut result = (self_log2 / base_log2);

// Converting u256 to u32, this cannot fail as the result will be atmost ~256
let parts = asm(r1: result) {
r1: (u64, u64, u64, u64)
};
let res_u32 = asm(r1: parts.3) {
r1: u32
};

// Raising the base to the power of the result
let mut pow_res = base.pow(res_u32);
let mut of = overflow();

// Adjusting the result until the power is less than or equal to self
// If pow_res is > than self, then there is an overestimation. If there is an overflow then there is definitely an overestimation.
while (pow_res > self) || (of > 0) {
result -= 1;

// Converting u256 to u32, this cannot fail as the result will be atmost ~256
let parts = asm(r1: result) {
r1: (u64, u64, u64, u64)
};
let res_u32 = asm(r1: parts.3) {
r1: u32
};

pow_res = base.pow(res_u32);
of = overflow();
};

set_flags(flags);

result
}
}

Expand Down
Loading

0 comments on commit aad97e8

Please sign in to comment.