Числовые операции

Описывает целочисленные операции wrap, checked, сдвиг, ошибки преобразования типов и результаты с плавающей запятой.

Wave Foundation

целочисленное представление

Битовое целое число N имеет биты значения N. Целые числа без знака находятся в диапазоне от 0 до 2^N - 1, а целые числа со знаком - от -2^(N-1) до 2^(N-1) - 1. signedness операции определяет интерпретацию битовой строки.

В таблице ниже поясняются результаты расчетов. IR В примере кода используется текущее представление принтера.

Сложение, вычитание, умножение

Базовые целые числа add·sub·mul содержат младшие биты N результата. overflow не вызывает trap. Операция checked возвращает тот же результат wrap, а также Bool, который указывает, находится ли математический результат за пределами диапазона соответствующего signed или unsigned.

операция Wrap Результат Checked overflow
u8: 255 + 1 0 true
i8: 127 + 1 −128 true
u8: 0 − 1 255 true
i8: 12 × 3 36 false

Интерфейсы для языков, которые прерывают выполнение на overflow, должны использовать явный trap_if для результата overflow операции checked. Операция по умолчанию не применяет неявно политику overflow исходного языка.

Wrap и IR выражают явную проверку

Следующие модули настроены как Rust builder и представляют собой текущие выходные данные принтера, прошедшие проверку. Анализатор текста и механизм выполнения пока недоступны.

module {
  format_version 2
  semantics_version 1
  target "x86_64-whale-linux"
  datalayout { ptr=64, endian=little }

  declare @f0 "add_u8": whale () -> u8, linkage internal
  declare @f1 "require_no_overflow": whale () -> u8, linkage internal

  fn @add_u8() -> u8, id @f0 {
  entry:
    %v0: u8 = const u8 255
    %v1: u8 = const u8 1
    %v2: u8 = add u8 %v0, %v1
    ret u8 %v2
  }

  fn @require_no_overflow() -> u8, id @f1 {
  entry:
    %v3: u8 = const u8 255
    %v4: u8 = const u8 1
    %v5: tuple<u8, bool> = uadd_chk u8 %v3, %v4
    %v6: u8 = extract %v5, 0
    %v7: bool = extract %v5, 1
    trap_if bool %v7, reason="integer overflow"
    ret u8 %v6
  }

}

В соответствии с арифметическим контрактом add_u8 преобразует 256 в 8-битный результат 0. require_no_overflow извлекает завернутый результат с помощью extract ..., 0 и флага переполнения Bool с помощью extract ..., 1. Если флаг true, trap_if останавливает выполнение перед возвратом. Это указанные результаты, а не вывод реализованного интерпретатора.

Деление и остаток

Целочисленное деление или остаток на ноль вызывает ловушку. Разделив минимальное значение со знаком на -1, мы получим минимальное значение. Остаток в этом случае равен нулю.

операция результат
i8: −128 / −1 −128
i8: −128 % −1 0
Разделить на целое число 0 trap
остаток для целого числа 0 trap

сдвиг

При сдвиге битового значения N битовая строка count интерпретируется как unsigned, и используется остаток, разделенный на N. Он не генерирует trap только потому, что count находится вне диапазона от 0 до N-1.

В 8-битных значениях count 0·8·16 все представляют собой 0-битные сдвиги. Битовая строка 11111111 из 8 бит count сдвигается на 7 бит. Это то же самое, даже если эта битовая строка представляет собой signed -1. unsigned Анализ выполняется перед остальными расчетами.

Исходные языки, которые отвергают отрицательные или чрезмерные count, должны поставить явную проверку перед сдвигом.

преобразование типов

преобразование смысл
Zero extension Увеличьте ширину, заполнив старшие биты 0.
Sign extension Увеличьте ширину, дублируя знаковый бит.
резка бит Сохраняйте только младшие биты, соответствующие ширине назначения.
Ударная реинтерпретация Интерпретация одной и той же битовой строки как разных типов
Числовое преобразование без потерь Если его невозможно выразить, сохранив числовое значение, trap

Например, преобразование 8-битного 11111111 в 16-битное zero extension становится 0000000011111111, а sign extension становится 1111111111111111. Даже если входные биты одинаковы, это разные операции.

Преобразование float-to-int отбрасывает дробную часть в направлении нуля, затем проверяется диапазон целых чисел. NaN и бесконечность вызывают ловушку. При преобразовании в i8 127,9 становится 127, а 128,0 — ловушкой. Bool преобразуется в целое число 0 или 1. Знак i1 не может представлять 1, поэтому он не может быть местом назначения этого преобразования.

Преобразование адреса в целое число не восстанавливает доступ указателя к этому целому числу. Пожалуйста, обратитесь к достоверность указателя.

арифметика с плавающей запятой

Значения с плавающей запятой имеют точную битовую строку f16·f32·f64. Операция округляет до ближайшего значения в объявленной ширине и, если ровно посередине, использует ties-to-even, которое выбирает значение с четными младшими битами значащих цифр.

Операция по умолчанию — fast-math, неявная FMA, которая не позволяет принудительно обнулять небольшие значения. Умножение, за которым следует сложение, сохраняет соответствующие шаги округления, и серверная часть не должна неявно объединять их в одну операцию.

Результатом числовой операции может быть NaN или бесконечность. NaN от математических операций и изменений ширины нормализуются до одного фиксированного количества тихих NaN на ширину. Сохраните и скопируйте исходные биты NaN. Поэтому поведение различается при передаче полезных данных NaN в память без арифметических операций и при их вычислении.

Он не предоставляет флаги состояния с плавающей запятой. Несмотря на то, что базовая арифметика с плавающей запятой позволяет получить результат NaN·бесконечность, преобразование float→int применяет приведенные выше правила trap.

Храните точные константы

Используйте FloatBits variant или любую строку шестнадцатеричных бит точной ширины. Равенство сохраненных значений сравнивается с битовой строкой, содержащей отрицательные 0 и NaN payload. Верификатор отклоняет, если ширина типов IR и payload различна.

use ir::{FloatBits, ModuleBuilder, Target, Type};
fn main() {
    let bits = FloatBits::parse(32, "0xffc01234").unwrap();
    assert_eq!(bits, FloatBits::F32(0xffc01234));
    let target = Target::X86_64WhaleLinux;
    let mut module = ModuleBuilder::new(target.name(), target.data_layout());
    let mut function = module.begin_function("payload", vec![], Type::F32);
    let value = function.const_float_bits(Type::F32, bits);
    function.ret(Some(value));
    function.finish();
    let module = module.finish();
    ir::verify_module(&module).unwrap();
    assert!(ir::print_module(&module).contains("const f32 0xffc01234"));
    println!("{}", bits);
}
0xffc01234

Битовая строка f16, f32 или f64 начинается с 0x, за которой следуют ровно 4, 8 или 16 шестнадцатеричных цифр соответственно. 0x80000000 представляет собой f32 отрицательный ноль; 0x7f800000 представляет положительную бесконечность. const_float преобразует значение хоста f64 в числовую форму; используйте const_float_bits, чтобы сохранить исходные биты. Точное представление хранилища не подразумевает полную обработку операций с плавающей запятой. Арифметика времени компиляции по-прежнему использует промежуточные звенья f64 хоста, поэтому контракт полного округления для каждой объявленной ширины еще не реализован.