diff --git a/docs/pragma_usage.rst b/docs/pragma_usage.rst
index e8e4e18..3c56768 100644
--- a/docs/pragma_usage.rst
+++ b/docs/pragma_usage.rst
@@ -356,6 +356,35 @@ Use the ``notify`` keyword in the ``update`` setting to enable this:
 records will be processed at a rate of 1 Hz/the IOC-configured poll rate.
 
 
+Scale and Offset
+................
+
+Integer and floating point values may have an EPICS-side scaling applied.
+
+Example:
+
+.. code-block:: none
+
+    scale: 3.0
+    offset: 1.0
+
+Values will be scaled according to the following:
+
+.. code-block:: none
+
+    readback_value = raw_value * scale + offset
+    setpoint_value = (user_value - offset) / scale
+
+.. note::
+
+    If either ``scale`` or ``offset`` are applied to an integer symbol, the
+    generated EPICS record type will no longer be a "long" integer input/output
+    record but rather change to an analog input/output record.
+
+    Keep this in mind if using advanced "field" directives in your pragmas.
+
+If unspecified, ``scale`` will be assumed to be 1.0 and ``offset`` 0.0.
+
 Archiver settings
 .................
 
diff --git a/pytmc/pragmas.py b/pytmc/pragmas.py
index 361fec1..821959c 100644
--- a/pytmc/pragmas.py
+++ b/pytmc/pragmas.py
@@ -624,6 +624,14 @@ def parse_array_settings(pragma, dimensions):
         )
 
 
+# Helpers which normalize various pragma values.
+_normalizers = {
+    'io': (normalize_io, 'io'),
+    'update': (parse_update_rate, '1s poll'),
+    'archive': (parse_archive_settings, '1s scan'),
+}
+
+
 def normalize_config(config):
     '''
     Parse and normalize pragma values into Python representations
@@ -640,12 +648,8 @@ def normalize_config(config):
     dict
         A shallow-copy of ``config`` with parsed and normalized values
     '''
-    key_to_parser = {'io': (normalize_io, 'io'),
-                     'update': (parse_update_rate, '1s poll'),
-                     'archive': (parse_archive_settings, '1s scan'),
-                     }
     ret = dict(config)
-    for key, (parser_func, default) in key_to_parser.items():
+    for key, (parser_func, default) in _normalizers.items():
         ret[key] = parser_func(ret.get(key, default))
     return ret
 
diff --git a/pytmc/record.py b/pytmc/record.py
index 6cc36d9..84500d7 100644
--- a/pytmc/record.py
+++ b/pytmc/record.py
@@ -306,6 +306,12 @@ class RecordPackage:
                     f'Unsupported data type {data_type.name} in chain: '
                     f'{chain.tcname} record: {chain.pvname}'
                 ) from None
+            if spec is IntegerRecordPackage:
+                if "scale" in chain.config or "offset" in chain.config:
+                    # longin/longout do not support scaling. Special-case and
+                    # cast to a FloatRecordPackage.
+                    spec = FloatRecordPackage
+
         return spec(*args, chain=chain, **kwargs)
 
 
@@ -572,6 +578,37 @@ class FloatRecordPackage(TwincatTypeRecordPackage):
                        pass1={}),
     }
 
+    def get_scale_offset(self):
+        """Get the scale and offset for the analog record(s)."""
+        scale = self.config.get("scale", None)
+        offset = self.config.get("offset", None)
+        if scale is None and offset is None:
+            return None, None
+
+        scale = "1.0" if scale is None else scale
+        offset = offset or "0.0"
+        return scale, offset
+
+    def generate_input_record(self):
+        record = super().generate_input_record()
+        scale, offset = self.get_scale_offset()
+        if scale is not None:
+            # If LINR==SLOPE, VAL = RVAL * ESLO + EOFF
+            record.fields["LINR"] = "SLOPE"
+            record.fields["ESLO"] = scale
+            record.fields["EOFF"] = offset
+        return record
+
+    def generate_output_record(self):
+        record = super().generate_output_record()
+        scale, offset = self.get_scale_offset()
+        if scale is not None:
+            # If LINR==SLOPE, then RVAL = (VAL - EOFF) / ESLO
+            record.fields["LINR"] = "SLOPE"
+            record.fields["ESLO"] = scale
+            record.fields["EOFF"] = offset
+        return record
+
 
 class EnumRecordPackage(TwincatTypeRecordPackage):
     """Create a set of record for a ENUM Twincat Variable"""

diff --git a/pytmc/tests/test_xml_collector.py b/pytmc/tests/test_xml_collector.py
index b53963d..d05f52a 100644
--- a/pytmc/tests/test_xml_collector.py
+++ b/pytmc/tests/test_xml_collector.py
@@ -687,3 +687,51 @@ def test_sub_io_change(dimensions, pragma, expected_records):
                     for pkg in pragmas.record_packages_from_symbol(outer)
                     for record in pkg.records)
     assert set(record_names) == expected_records
+
+
+@pytest.mark.parametrize(
+    "data_type, pragma, expected_scale, expected_offset",
+    [
+        pytest.param(
+            "FLOAT",
+            "pv: PREFIX; scale: 2.0; offset: 1.0",
+            "2.0",
+            "1.0",
+            id="float-scale-and-offset",
+        ),
+        pytest.param(
+            "UDINT",
+            "pv: PREFIX; scale: 3.0; offset: 0.1",
+            "3.0",
+            "0.1",
+            id="int-scale-and-offset",
+        ),
+        pytest.param(
+            "UDINT",
+            "pv: PREFIX; scale: 3.0",
+            "3.0",
+            "0.0",
+            id="int-no-offset",
+        ),
+        pytest.param(
+            "UDINT",
+            "pv: PREFIX; offset: 3.0",
+            "1.0",
+            "3.0",
+            id="int-no-scale",
+        ),
+    ],
+)
+def test_scale_and_offset(data_type, pragma, expected_scale, expected_offset):
+    item = make_mock_twincatitem(
+        name='Main.obj',
+        data_type=make_mock_type('UDINT', is_complex_type=False),
+        pragma=pragma,
+    )
+
+    pkg, = list(pragmas.record_packages_from_symbol(item))
+    for rec in pkg.records:
+        assert rec.fields['LINR'] == "SLOPE"
+        assert rec.fields['ESLO'] == expected_scale
+        assert rec.fields['EOFF'] == expected_offset
+        assert rec.record_type in {"ai", "ao"}

Pragma enhancement: "scale" key
Request
---------

Ability to scale integer (or floating point) values from the PLC on the EPICS side in a consistent way.

"scale" key
-----------

e.g., 
```
scale: 1.234
offset: 10.0
```

Presence of a `scale` key on a record normally generated as `longin` would then create an `ai` record instead.

Alternative: `calc` key
--------------------

e.g., `calc: 2 * value + 1.54`

Maybe consider a `calc` field which may allow generic calculations by way of an auto-generated `calc` record.
This could then allow for easy slope, offset, and _perhaps_ even references to other symbols. (That last part is iffy, though)

cc @slacAWallace 
pytmc/tests/test_xml_collector.py::test_scale_and_offset[float-scale-and-offset] pytmc/tests/test_xml_collector.py::test_scale_and_offset[int-scale-and-offset] pytmc/tests/test_xml_collector.py::test_scale_and_offset[int-no-offset] pytmc/tests/test_xml_collector.py::test_scale_and_offset[int-no-scale]
pytmc/tests/test_xml_collector.py::test_record_package_from_chain[BOOL-False-BinaryRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[BOOL-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[INT-False-IntegerRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[INT-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[DINT-False-IntegerRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[DINT-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[ENUM-False-EnumRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[ENUM-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[REAL-False-FloatRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[REAL-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[LREAL-False-FloatRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[LREAL-True-WaveformRecordPackage] pytmc/tests/test_xml_collector.py::test_record_package_from_chain[STRING-False-StringRecordPackage] pytmc/tests/test_xml_collector.py::test_dtype[BOOL-i-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[BOOL-io-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[BOOL-i-True-asynInt8ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[BOOL-io-True-asynInt8ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[INT-i-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[INT-io-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[INT-i-True-asynInt16ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[INT-io-True-asynInt16ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[DINT-i-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[DINT-io-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[DINT-i-True-asynInt32ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[DINT-io-True-asynInt32ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[REAL-i-False-asynFloat64] pytmc/tests/test_xml_collector.py::test_dtype[REAL-io-False-asynFloat64] pytmc/tests/test_xml_collector.py::test_dtype[REAL-i-True-asynFloat32ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[REAL-io-True-asynFloat32ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[LREAL-i-False-asynFloat64] pytmc/tests/test_xml_collector.py::test_dtype[LREAL-io-False-asynFloat64] pytmc/tests/test_xml_collector.py::test_dtype[LREAL-i-True-asynFloat64ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[LREAL-io-True-asynFloat64ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[ENUM-i-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[ENUM-io-False-asynInt32] pytmc/tests/test_xml_collector.py::test_dtype[ENUM-i-True-asynInt16ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[ENUM-io-True-asynInt16ArrayOut] pytmc/tests/test_xml_collector.py::test_dtype[STRING-i-False-asynInt8ArrayIn] pytmc/tests/test_xml_collector.py::test_dtype[STRING-io-False-asynInt8ArrayOut] pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[BYTE-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[BYTE-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[SINT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[SINT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[USINT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[USINT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[WORD-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[WORD-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[INT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[INT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[UINT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[UINT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[DWORD-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[DWORD-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[DINT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[DINT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[UDINT-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[UDINT-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[LREAL-0--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[LREAL-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[STRING-2--INP-@asyn($(PORT),0,1)ADSPORT=851/POLL_RATE=1/a.b.c?] pytmc/tests/test_xml_collector.py::test_input_output_scan[STRING-6--OUT-@asyn($(PORT),0,1)ADSPORT=851/a.b.c=] pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-1hz pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-2hz pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-0.5hz pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-0.02hz pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-0.1hz pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-50s pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-10s pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-2s pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-1s pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-0.5s pytmc/tests/test_xml_collector.py::test_input_output_scan[BOOL-2-0.1s pytmc/tests/test_xml_collector.py::test_bool_naming[BOOL-0-FALSE-TRUE-True] pytmc/tests/test_xml_collector.py::test_bool_naming[STRING-0-None-None-False] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_PREC[LREAL-0-3-True] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_PREC[STRING-0-None-False] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[INT-o-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[BOOL-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[BOOL-i-False-True-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[BOOL-o-False-True-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[BOOL-io-False-True-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[INT-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[INT-i-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[INT-o-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[INT-io-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[DINT-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[DINT-i-False-True-LONG] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[DINT-o-False-True-LONG] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[DINT-io-False-True-LONG] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[ENUM-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[ENUM-i-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[ENUM-o-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[ENUM-io-False-True-SHORT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[REAL-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[REAL-i-False-True-FLOAT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[REAL-o-False-True-FLOAT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[REAL-io-False-True-FLOAT] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[LREAL-i-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[LREAL-i-False-True-DOUBLE] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[LREAL-o-False-True-DOUBLE] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[LREAL-io-False-True-DOUBLE] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[STRING-i-True-False-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[STRING-o-True-False-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_FTVL[STRING-io-True-False-CHAR] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_NELM[INT-0-False-False-None] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_NELM[INT-0-False-True-3] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_NELM[LREAL-0-False-True-9] pytmc/tests/test_xml_collector.py::test_BaseRecordPackage_guess_NELM[STRING-0-True-False-81] pytmc/tests/test_xml_collector.py::test_scalar pytmc/tests/test_xml_collector.py::test_complex_array pytmc/tests/test_xml_collector.py::test_complex_array_partial[no-pragma] pytmc/tests/test_xml_collector.py::test_complex_array_partial[0..1] pytmc/tests/test_xml_collector.py::test_complex_array_partial[0..1,3] pytmc/tests/test_xml_collector.py::test_complex_array_partial[..1] pytmc/tests/test_xml_collector.py::test_complex_array_partial[1..] pytmc/tests/test_xml_collector.py::test_complex_array_partial[0..1,99..] pytmc/tests/test_xml_collector.py::test_complex_array_partial[separate_prefixes] pytmc/tests/test_xml_collector.py::test_enum_array pytmc/tests/test_xml_collector.py::test_unroll_formatting pytmc/tests/test_xml_collector.py::test_pv_linking pytmc/tests/test_xml_collector.py::test_pv_linking_special pytmc/tests/test_xml_collector.py::test_waveform_archive[over_threshold] pytmc/tests/test_xml_collector.py::test_waveform_archive[under_threshold] pytmc/tests/test_xml_collector.py::test_complex_sub_array_partial[no-pragma] pytmc/tests/test_xml_collector.py::test_complex_sub_array_partial[0..1] pytmc/tests/test_xml_collector.py::test_complex_sub_array_partial[1..3,5,7,9] pytmc/tests/test_xml_collector.py::test_sub_io_change[no-pragma] pytmc/tests/test_xml_collector.py::test_sub_io_change[change_to_io]