Js целочисленное деление: Как поделить число нацело в javascript?

javascript — Зачем при записи чисел в конце добавляют .0?

Вопрос задан

Изменён 5 лет 4 месяца назад

Просмотрен 783 раза

Часто в коде программ числа записываются в виде цифры с .0 на конце. Например:

C: double s = 1.0 / 6.0;

Delphi: var d: Single; begin d := 16.0 + 1.0; end;

JS: var t = 90.0;

Возможны примеры и на других языках, но для начала предлагаю ограничиться этими тремя

Какой смысл несет добавление .0 в данных случаях?
В каких случаях .0 можно опускать, а в каких он меняет логику поведения программы?

  • javascript
  • c++
  • delphi

6

Если говорить в общем, то добавление . 0 к числу, изменяет его тип и превращает его из целого, в дробное (число с плавающей точкой). Такое изменение типа числа влияет на то, как оно будет храниться в памяти и на то, как и какие операции с ним могут выполняться.

В JS добавление .0 абсолютно лишено всякого смысла, т.к. там все числа изначально являются дробными.

В Delphi так же не возникает необходимости вручную приводить целые числа к дробным, т.к. компилятор замечательно справляется с этим самостоятельно, там где это нужно. Операция деления двух целых всегда возвращает дробное число и его невозможно по ошибке присвоить целому. Для особых случаев, когда надо выполнить целочисленное деление, есть специальный оператор div.

А вот в Си и некоторых других языках, и для целочисленного, и для обычного деления используется один и тот же оператор деления

/ который ведёт себя по разному, в зависимости от типа операндов:

  • если оба операнда целые, то и в результате деления будет целое (целочисленное деление): double i = 5 / 2; даст 2. 0 (дробная часть результата операции отбрасывается, получается целое число, которое затем приводится к целевому типу double).
  • если хоть один из операндов является дробным, то и результат будет дробным: double i = 5 / 2.0; даст 2.5.

В Си, вместо .0 перед числом можно указывать тип числа с плавающей точкой (float/double), т.е. выполнять приведение типа: double i = 5 / (double) 2; такая конструкция используется, если операнд не число, а переменная целочисленного типа:

int k = 2;
double i = 5 / (double) k; // --> i = 2.5

2

В конце литерала нужно добавлять .0 в том случае, если вам важно получить тип с плавающей точкой. Я приведу пример для С/С++, но он также будет актуальным для Java, C# и многих других языков:

double X = 15 / 2; // Выведет 7

Здесь тип переменной — double, так что можно подумать, что в ответе получится 7. 5, но это не так. Поскольку и 15 и 2 не имеют дробной части, они будут считаться целыми числами при вычислении, и для них сначала будет произведено целочисленное деление (15 / 2 = 7), а потом полученный результат будет конвертирован и записан в переменную типа double.

Это можно исправить, добавив пустую дробную часть к одному или к обоим литералам. Если один из операндов является числом с плавающей точкой, то второй операнд тоже будет конвертирован в число с плавающей точкой, и вы получите правильный результат:

double X = 15.0 / 2; // Выведет 7.5

Общее правило для C++ и подобных языков: добавляйте .0 к литералам тогда, когда ожидаете ответ типа float или double. Это поможет избежать неожиданностей.

7

.0 задавать для значения переменной нужно для того, что бы явно показать как компилятору (интерпретатору) языка, так и программисту, что в контексте использования переменной речь идет о дробных числах. Один из возможных примеров тут.

  1. Изучайте базовые типы и приемы работы с переменными этих типов для тех платформ и языков, на которых программируете или собираетесь начать программировать.

  2. Если у вас есть вероятность, что в результатах ваших вычислений будут задействованы дробные числа — так и работайте с переменными соответствующих типов.

6

В давние времена, когда появился Fortran — первый язык программирования высокого уровня, имеющий транслятор, это были азы, с которых начиналось обучение программированию. Добавление точки после числа сейчас называют неявным приведением типа. Когда надо было получить тип REAL от деления целых, писали Z=(X+0.)/(Y+0.) или Z=7./8.

Это, конечно, было неисчерпаемым источником багов в программах. Тогда считалось несолидным добавлять ноль после точки. Это же целая лишняя дырка в перфокарте!

Зарегистрируйтесь или войдите

Регистрация через Google

Регистрация через Facebook

Регистрация через почту

Отправить без регистрации

Почта

Необходима, но никому не показывается

Отправить без регистрации

Почта

Необходима, но никому не показывается

Нажимая на кнопку «Отправить ответ», вы соглашаетесь с нашими пользовательским соглашением, политикой конфиденциальности и политикой о куки

Основные типы — Kotlin

В Kotlin всё является объектом, в том смысле, что пользователь может вызвать функцию или получить доступ к свойству любой переменной.

Некоторые типы являются встроенными, т.к. их реализация оптимизирована, хотя для пользователя они выглядят как обычные классы. В данном разделе описываются основные типы: числа, логические переменные, символы, строки и массивы.

Числа

Целочисленные типы

В Kotlin есть набор встроенных типов, которые представляют числа. Для целых чисел существует четыре типа с разными размерами и, следовательно, разными диапазонами значений.

ТипРазмер (биты)Минимальное значениеМаксимальное значение
Byte8-128127
Short16-32768 32767
Int32-2,147,483,648 (-231)2,147,483,647 (231 — 1)
Long64-9,223,372,036,854,775,808 (-263)9,223,372,036,854,775,807 (263 — 1)

Все переменные, инициализированные целыми значениями, не превышающими максимальное значение Int, имеют предполагаемый тип Int. Если начальное значение превышает это значение, то тип Long. Чтобы явно указать тип Long, добавьте после значения L.

val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1

Типы с плавающей точкой

Для действительных чисел в Kotlin есть типы с плавающей точкой Float

и Double. Согласно стандарту IEEE 754, типы с плавающей точкой различаются своим десятичным разрядом, то есть количеством десятичных цифр, которые они могут хранить. С точки зрения IEEE 754 Float является одинарно точным, а Double обеспечивает двойную точность.

ТипРазмер (биты)Значимые битыБиты экспонентыРазряды
Float322486-7
Double64531115-16

Вы можете инициализировать переменные Double и Float числами, имеющими дробную часть. Она должна быть отделена от целой части точкой (

.). Для переменных, инициализированных дробными числами, компилятор автоматически определяет тип Double.

val pi = 3.14 // Double
// val one: Double = 1 // Ошибка: несоответствие типов
val oneDouble = 1.0 // Double

Чтобы явно указать тип Float, добавьте после значения f или F. Если такое значение содержит более 6-7 разрядов, оно будет округлено.

val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, фактическое значение 2.7182817

Обратите внимание, что в отличие от некоторых других языков, в Kotlin нет неявных преобразований для чисел. Например, функция с Double параметром может вызываться только для Double, но не для Float, Int или других числовых значений.

fun main() {
    fun printDouble(d: Double) { print(d) }
    val i = 1    
    val d = 1.0
    val f = 1.0f 
    printDouble(d)
//  printDouble(i) // Ошибка: несоответствие типов
//  printDouble(f) // Ошибка: несоответствие типов
}

Чтобы преобразовать числовые значения в различные типы, используйте Явные преобразования.

Символьные постоянные

В языке Kotlin присутствуют следующие виды символьных постоянных (констант) для целых значений:

  • Десятичные числа: 123
    • Тип Long обозначается заглавной L: 123L
  • Шестнадцатеричные числа: 0x0F
  • Двоичные числа: 0b00001011

ВНИМАНИЕ: Восьмеричные литералы не поддерживаются.

Также Kotlin поддерживает числа с плавающей запятой:

  • Тип Double по умолчанию: 123.5, 123.5e10
  • Тип Float обозначается с помощью f или F: 123.5f

Вы можете использовать нижние подчеркивания, чтобы сделать числовые константы более читаемыми:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

Представление чисел в JVM

Обычно платформа JVM хранит числа в виде примитивных типов: int, double и так далее. Если же вам необходима ссылка, которая может принимать значение null (например, Int?), то используйте обёртки. В этих случаях числа помещаются в Java классы как Integer, Double и так далее.

Обратите внимание, что использование обёрток для одного и того же числа не гарантирует равенства ссылок на них.

val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false

Все nullable-ссылки на a на самом деле являются одним и тем же объектом из-за оптимизации памяти, которую JVM применяет к Integer между «-128» и «127». Но b больше этих значений, поэтому ссылки на b являются разными объектами.

Однако, равенство по значению сохраняется.

val b: Int = 10000
println(b == b) // Prints 'true'
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // Prints 'true'

Явные преобразования

Из-за разницы в представлениях меньшие типы не являются подтипами бОльших типов. В противном случае возникли бы сложности.

// Возможный код, который на самом деле не скомпилируется:
val a: Int? = 1 // "Обёрнутый" Int (java.lang.Integer)
val b: Long? = a // неявное преобразование возвращает "обёрнутый" Long (java.lang.Long)
print(b == a) // Нежданчик! Данное выражение выведет "false" т. к. метод equals() типа Long предполагает, что вторая часть выражения также имеет тип Long

Таким образом, будет утрачена не только тождественность (равенство по ссылке), но и равенство по значению.

Как следствие, неявное преобразование меньших типов в большие НЕ происходит. Это значит, что мы не можем присвоить значение типа Byte переменной типа Int без явного преобразования.

val b: Byte = 1 // всё хорошо, литералы проверяются статически
// val i: Int = b // ОШИБКА
val i1: Int = b.toInt()

Каждый численный тип поддерживает следующие преобразования:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

Часто необходимости в явных преобразованиях нет, поскольку тип выводится из контекста, а арифметические действия перегружаются для подходящих преобразований.

val l = 1L + 3 // Long + Int => Long

Операции

Котлин поддерживает стандартный набор арифметических операций над числами: +, -, *, /, %. Они объявляются членами соответствующих классов.

println(1 + 2)
println(2_500_000_000L - 1L)
println(3.14 * 2.71)
println(10.0 / 3)

Вы также можете переопределить эти операторы для пользовательских классов. См. Перегрузка операторов для деталей.

Деление целых чисел

Деление целых чисел всегда возвращает целое число. Любая дробная часть отбрасывается.

val x = 5 / 2
// println(x == 2.5) // ОШИБКА: Оператор '==' не может быть применен к 'Int' и 'Double'
println(x == 2) // true

Это справедливо для деления любых двух целочисленных типов.

val x = 5L / 2
println(x == 2L) // true

Чтобы вернуть тип с плавающей точкой, явно преобразуйте один из аргументов в тип с плавающей точкой.

val x = 5 / 2.toDouble()
println(x == 2. 5) // true
Побитовые операции

Kotlin поддерживает обычный набор побитовых операций над целыми числами. Они работают на двоичном уровне непосредственно с битовыми представлениями чисел. Побитовые операции представлены функциями, которые могут быть вызваны в инфиксной форме. Они могут быть применены только к Int и Long.

val x = (1 shl 2) and 0x000FF000

Ниже приведён полный список битовых операций:

  • shl(bits) – сдвиг влево с учётом знака (<< в Java)
  • shr(bits) – сдвиг вправо с учётом знака (>> в Java)
  • ushr(bits) – сдвиг вправо без учёта знака (>>> в Java)
  • and(bits) – побитовое И
  • or(bits) – побитовое ИЛИ
  • xor(bits) – побитовое исключающее ИЛИ
  • inv() – побитовое отрицание

Сравнение чисел с плавающей точкой

В этом разделе обсуждаются следующие операции над числами с плавающей запятой:

  • Проверки на равенство: a == b и a != b
  • Операторы сравнения: a < b, a > b, a <= b, a >= b
  • Создание диапазона и проверка диапазона: a. .b, x in a..b, x !in a..b

Когда статически известно, что операнды a и b являются Float или Double или их аналогами с nullable-значением (тип объявлен или является результатом умного приведения), операции с числами и диапазоном, который они образуют, соответствуют стандарту IEEE 754 для арифметики с плавающей точкой.

Однако для поддержки общих вариантов использования и обеспечения полного упорядочивания, когда операнды статически не объявлены как числа с плавающей запятой (например, Any, Comparable<...>, параметр типа), операции используют реализации equals и compareTo для Float и Double, которые не согласуются со стандартом, так что:

  • NaN считается равным самому себе
  • NaN считается больше, чем любой другой элемент, включая «POSITIVE_INFINITY»
  • -0. 0 считается меньше, чем 0.0

Целые беззнаковые числа

В дополнение к целочисленным типам, в Kotlin есть следующие типы целых беззнаковых чисел:

  • UByte: беззнаковое 8-битное целое число, в диапазоне от 0 до 255
  • UShort: беззнаковое 16-битное целое число, в диапазоне от 0 до 65535
  • UInt: беззнаковое 32-битное целое число, в диапазоне от 0 до 232 — 1
  • ULong: беззнаковое 64-битное целое число, в диапазоне от 0 до 264 — 1

Беззнаковые типы поддерживают большинство операций своих знаковых аналогов.

Изменение типа с беззнакового типа на его знаковый аналог (и наоборот) является двоично несовместимым изменением.

Беззнаковые массивы и диапазоны

Беззнаковые массивы и операции над ними находятся в стадии бета-тестирования. Они могут быть несовместимо изменены в любое время.

Как и в случае с примитивами, каждому типу без знака соответствует тип массивов знаковых типов:

  • UByteArray: массив беззнаковых byte
  • UShortArray: массив беззнаковых short
  • UIntArray: массив беззнаковых int
  • ULongArray: массив беззнаковых long

Как и целочисленные массивы со знаком, такие массивы предоставляют API, аналогичный классу Array, без дополнительных затрат на оборачивание.

При использовании массивов без знака вы получите предупреждение, что эта функция еще не стабильна. Чтобы удалить предупреждение используйте аннотацию @ExperimentalUnsignedTypes. Вам решать, должны ли ваши пользователи явно соглашаться на использование вашего API, но имейте в виду, что беззнаковый массив не является стабильной функцией, поэтому API, который он использует, может быть нарушен изменениями в языке. Узнайте больше о требованиях регистрации.

Диапазоны и прогрессии поддерживаются для UInt и ULong классами UIntRange,UIntProgression, ULongRange и ULongProgression. Вместе с целочисленными беззнаковыми типами эти классы стабильны.

Литералы

Чтобы целые беззнаковые числа было легче использовать, в Kotlin можно помечать целочисленный литерал суффиксом, указывающим на определенный беззнаковый тип (аналогично Float или Long):

  • u и U помечают беззнаковые литералы. Точный тип определяется на основе ожидаемого типа. Если ожидаемый тип не указан, компилятор будет использовать UInt или ULong в зависимости от размера литерала.
val b: UByte = 1u  // UByte, есть ожидаемый тип
val s: UShort = 1u // UShort, есть ожидаемый тип
val l: ULong = 1u  // ULong, есть ожидаемый тип
val a1 = 42u // UInt: ожидаемого типа нет, константе подходит тип UInt
val a2 = 0xFFFF_FFFF_FFFFu // ULong: ожидаемого типа нет, тип UInt не подходит константе
  • uL and UL явно помечают литерал как unsigned long.
val a = 1UL // ULong, даже несмотря на то, что ожидаемого типа нет и константа вписывается в UInt
Дальнейшее обсуждение

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

Логический тип

Тип Boolean представляет логический тип данных и принимает два значения: true и false.

При необходимости использования nullable-ссылок логические переменные оборачиваются Boolean?.

Встроенные действия над логическими переменными включают:

  • || – ленивое логическое ИЛИ
  • && – ленивое логическое И
  • ! – отрицание
val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null
println(myTrue || myFalse)
println(myTrue && myFalse)
println(!myTrue)

В JVM: nullable-ссылки на логические объекты заключены в рамки аналогично числам.

Символы

Символы в Kotlin представлены типом Char. Символьные литералы заключаются в одинарные кавычки: '1'.

Специальные символы начинаются с обратного слеша \. Поддерживаются следующие escape-последовательности: \t, \b, \n, \r, \', \", \\ и \$.

Для кодирования любого другого символа используйте синтаксис escape-последовательности Юникода: '\uFF00'.

val aChar: Char = 'a'
println(aChar)
println('\n') // выводит дополнительный символ новой строки
println('\uFF00')

Если значение символьной переменной – цифра, её можно явно преобразовать в Int с помощью функции digitToInt().

В JVM: Подобно числам, символы оборачиваются при необходимости использования nullable-ссылки. При использовании обёрток тождественность (равенство по ссылке) не сохраняется.

Строки

Строки в Kotlin представлены типом String. Как правило, строка представляет собой последовательность символов в двойных кавычках (").

val str = "abcd 123"

Строки состоят из символов, которые могут быть получены по порядковому номеру: s[i]. Проход по строке выполняется циклом for.

for (c in str) {
    println(c)
}

Строки являются неизменяемыми. После инициализации строки вы не можете изменить ее значение или присвоить ей новое. Все операции, преобразующие строки, возвращают новый объект String, оставляя исходную строку неизменной.

val str = "abcd"
println(str.uppercase()) // Создается и выводится новый объект String
println(str) // исходная строка остается прежней

Для объединения строк используется оператор +. Это работает и для объединения строк с другими типами, если первый элемент в выражении является строкой.

val s = "abc" + 1
println(s + "def") // abc1def

Обратите внимание, что в большинстве случаев использование строковых шаблонов или обычных строк предпочтительнее объединения строк.

Строковые литералы

В Kotlin представлены два типа строковых литералов:

  • экронированные строки с экранированными символами
  • обычные строки, которые могут содержать символы новой строки и произвольный текст

Вот пример экранированной строки:

val s = "Hello, world!\n"

Экранирование выполняется общепринятым способом, а именно с помощью обратного слеша (\). Список поддерживаемых escape-последовательностей см. в разделе Символы выше.

Обычная строка выделена тройной кавычкой ("""), не содержит экранированных символов, но может содержать символы новой строки и любые другие символы:

val text = """
  for (c in "foo")
    print(c)
"""

Чтобы удалить пробелы в начале обычных строк, используйте функцию trimMargin().

val text = """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin()

По умолчанию | используется в качестве префикса поля, но вы можете выбрать другой символ и передать его в качестве параметра, например, trimMargin(">").

Строковые шаблоны

Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ($) и состоит либо из простого имени (например, переменной),

val i = 10
println("i = $i") // выведет "i = 10"

либо из произвольного выражения в фигурных скобках.

val s = "abc"
println("$s.length is ${s.length}") // выведет "abc.length is 3"

Шаблоны поддерживаются как в обычных, так и в экранированных строках. При необходимости вставить символ $ в обычную строку (такие строки не поддерживают экранирование обратным слешом) перед любым символом, который разрешен в качестве начала идентификатора, используйте следующий синтаксис:

val price = """
${'$'}_9.99
"""

Массивы

Массивы в Kotlin представлены классом Array, обладающим функциями get и set (которые обозначаются [] согласно соглашению о перегрузке операторов), и свойством size, а также несколькими полезными встроенными функциями.

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit
    operator fun iterator(): Iterator<T>
    // ...
}

Для создания массива используйте функцию arrayOf(), которой в качестве аргумента передаются элементы массива, т. е. выполнение arrayOf(1, 2, 3) создаёт массив [1, 2, 3]. С другой стороны функция arrayOfNulls() может быть использована для создания массива заданного размера, заполненного значениями null.

Также для создания массива можно использовать фабричную функцию, которая принимает размер массива и функцию, возвращающую начальное значение каждого элемента по его индексу.

// создаёт массив типа Array<String> со значениями ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }

Как отмечено выше, оператор [] используется вместо вызовов встроенных функций get() и set().

Обратите внимание: в отличие от Java массивы в Kotlin являются инвариантными. Это значит, что Kotlin запрещает нам присваивать массив Array<String> переменной типа Array<Any>, предотвращая таким образом возможный отказ во время исполнения (хотя вы можете использовать Array<out Any>, см. Проекции типов).

Массивы примитивных типов

Также в Kotlin есть особые классы для представления массивов примитивных типов без дополнительных затрат на оборачивание: ByteArray, ShortArray, IntArray и т.д. Данные классы не наследуют класс Array, хотя и обладают тем же набором методов и свойств. У каждого из них есть соответствующая фабричная функция:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// int массив, размером 5 со значениями [0, 0, 0, 0, 0]
val arr = IntArray(5)
// инициализация элементов массива константой
// int массив, размером 5 со значениями [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// инициализация элементов массива лямбда-выражением
// int массив, размером 5 со значениями [0, 1, 2, 3, 4] (элементы инициализированы своим индексом)
var arr = IntArray(5) { it * 1 } 

Integer Division — Algorithmica

По сравнению с другими арифметическими операциями, деление очень плохо работает на x86 и компьютерах в целом. Общеизвестно, что как с плавающей запятой, так и с целым числом деление сложно реализовать на аппаратном уровне. Схема занимает много места в АЛУ, вычисления состоят из множества стадий, и в результате div и его собратья обычно занимают 10-20 циклов, при этом задержка немного меньше при меньших размерах данных.

#Деление и модуль в x86

Поскольку никто не хочет дублировать весь этот беспорядок для отдельной операции по модулю, инструкция div служит обеим целям. Чтобы выполнить 32-битное целочисленное деление, вам нужно поместить делимое , а именно , в регистр eax и вызвать div с делителем в качестве единственного операнда. После этого частное будет храниться в eax , а остаток будет храниться в edx .

Единственная оговорка заключается в том, что дивиденд на самом деле должен храниться в два регистра , eax и edx : этот механизм обеспечивает деление 64 на 32 или даже 128 на 64, аналогично тому, как работает 128-битное умножение. При выполнении обычного знакового деления 32 на 32 нам нужно расширить знак eax до 64 бит и сохранить его старшую часть в edx :

 div(int, int):
    мов акс, эди
    компакт-диск
    идив эси
    рет
 

Для беззнакового деления можно просто установить edx в ноль, чтобы не мешало:

 div(без знака, без знака):
    мов акс, эди
    xor эдкс, эдкс
    див эси
    рет
 

В обоих случаях в дополнение к частному в eax вы также можете получить доступ к остатку как edx :

 mod(без знака, без знака):
    мов акс, эди
    xor эдкс, эдкс
    див эси
    движение eax, edx
    рет
 

Вы также можете разделить 128-битное целое число (хранящееся в rdx:rax ) на 64-битное целое число:

 div(u128, u64):
    ; а = rdi + rsi, b = rdx
    мов rcx, rdx
    мов ракс, рди
    мов rdx, rsi
    раздел edx
    рет
 

Старшая часть делимого должна быть меньше делителя, иначе произойдет переполнение. Из-за этого ограничения трудно заставить компиляторы создавать этот код самостоятельно: если вы разделите 128-битный целочисленный тип на 64-битное целое число, компилятор заполнит его дополнительными проверками, которые на самом деле могут быть ненужными.

#Division by Constants

Целочисленное деление мучительно медленное, даже если оно полностью аппаратно реализовано, но в некоторых случаях его можно избежать, если делитель постоянный. Хорошо известным примером является деление на степень двойки, которое можно заменить двоичным сдвигом в один цикл: двоичный алгоритм GCD — восхитительная демонстрация этой техники. 99 + 7)$:

 ; ввод (rdi): х
; вывод (rax): x mod (m=1e9+7)
мов ракс, рди
movabs rdx, -8543223828751151131 ; загрузить магическую константу в регистр
мул рдкс ; выполнить умножение
мов ракс, rdx
Шр Ракс, 29 лет; двоичный сдвиг результата
 

Этот метод называется Редукция Барретта , и он называется «редукция», потому что он в основном используется для операций по модулю, которые могут быть заменены одним делением, умножением и вычитанием в силу этой формулы: 9с})$. Левая граница максимально близка к целому числу, а размер всего диапазона является вторым из возможных. И вот кульминация: если $s \ge n$, то единственное целое число, содержащееся в этом диапазоне, равно $1$, и поэтому алгоритм всегда будет возвращать его.

#Lemire Reduction

Сокращение по Барретту немного сложно, а также генерирует последовательность команд длины для модуля, поскольку она вычисляется косвенно. Существует новый (2019 г.) метод, который в некоторых случаях проще и на самом деле быстрее по модулю. У него пока нет общепринятого названия, но я буду называть его редукцией Лемира.

Вот основная идея. Рассмотрим представление с плавающей запятой некоторой целой дроби:

$$ \frac{179}{6} = 11101,1101010101\ldots = 29\tfrac{5}{6} \приблизительно 29,83 $$

Как мы можем «разобрать» его, чтобы получить нужные нам части?

  • Чтобы получить целую часть (29), мы можем просто уменьшить или обрезать ее до точки.
  • Чтобы получить дробную часть (⅚), мы можем просто взять то, что стоит после точек.
  • Чтобы получить остаток (5), мы можем умножить дробную часть на делитель. 964/г) uint32_t мод (uint32_t х) { uint64_t младшие биты = m * x; return ((__uint128_t) младшие биты * y) >> 64; } uint32_t дел (uint32_t х) { return ((__uint128_t) m * x) >> 64; }

    Мы также можем проверить делимость $x$ на $y$ всего одним умножением, используя тот факт, что остаток от деления равен нулю тогда и только тогда, когда дробная часть (младшие 64 бита $m \cdot x$) не превосходит $m$ (иначе оно стало бы ненулевым числом при обратном умножении на $y$ и сдвиге вправо на 64).

     логическое значение is_divisible (uint32_t x) {
        вернуть м * х < м;
    }
     

    Единственным недостатком этого метода является то, что для выполнения умножения требуются целочисленные типы, в четыре раза превышающие исходный размер, в то время как другие методы сокращения могут работать только с двойным числом.

    Существует также способ вычисления 64x64 по модулю путем тщательного манипулирования половинками промежуточных результатов; реализация оставлена ​​читателю в качестве упражнения.

    #Дополнительная литература

    Ознакомьтесь с libdivide и GMP, чтобы узнать о более общих реализациях оптимизированного целочисленного деления.

    Также стоит прочитать Hacker’s Delight, в котором есть целая глава, посвященная целочисленному делению.

    вершина - целочисленные деления всегда получают 0

    спросил

    Изменено 9 лет, 5 месяцев назад

    Просмотрено 7к раз

    При делении большего значения на меньшее допустимо. Но когда маленькое значение делится на большее значение, всегда становится 0,

    Целое число а = 123;

    Целое число b = 300;

    Двойной результат = 0,0;

    результат = (а/б) * 100;

    Для описанного выше метода всякий раз, когда я делаю деление, всегда получается 0.

    Есть идеи?

    1

    Сделайте как показано ниже -

     Double a = 123;
    Целое число b = 300;
    Двойной результат = 0,0;
    результат = (а/б) * 100;
     

    по крайней мере a или b должны быть объявлены как Double. если один из них двойной, то результат (a/b) будет двойным, и вы получите правильное значение. если оба являются целыми числами, тогда (a/b) дает целочисленный результат в вашем случае 0, а окончательный результат будет 0,

    Если оба операнда имеют тип Integer, то ваш '/' дает операцию целочисленного деления, а не деление с плавающей запятой/десятичное число, которое вы ожидали здесь. Не имеет значения, что ваш левый правопреемник является Decimal, потому что операция деления происходит до присваивания (деление имеет более высокую силу спроса, чем присваивание). По сути, при целочисленном делении десятичная часть результата (т. е. остаток) отсекается.

    Так работает и в C, и в Java; это не причуда Apex (на этот раз).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *